mirror of https://github.com/knative/docs.git
updating to the latest cloudevents sdk - v2.0.0-RC1 (#2386)
* updating to the latest cloudevents sdk. * ran lint * markdown had an error in it * update serving sample readme
This commit is contained in:
parent
2ce03cbb89
commit
fc69d36ec6
|
|
@ -26,25 +26,29 @@
|
|||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:902544577dcb868a5ae31529d73a1ce5031e224923290caedf6176241ee304e0"
|
||||
digest = "1:f2d3857204e90f618f155eef24533ac205b1ce7b3570e21502ad0f0c2f6c288e"
|
||||
name = "github.com/cloudevents/sdk-go"
|
||||
packages = [
|
||||
".",
|
||||
"pkg/cloudevents",
|
||||
"pkg/cloudevents/client",
|
||||
"pkg/cloudevents/context",
|
||||
"pkg/cloudevents/datacodec",
|
||||
"pkg/cloudevents/datacodec/json",
|
||||
"pkg/cloudevents/datacodec/text",
|
||||
"pkg/cloudevents/datacodec/xml",
|
||||
"pkg/cloudevents/observability",
|
||||
"pkg/cloudevents/transport",
|
||||
"pkg/cloudevents/transport/http",
|
||||
"pkg/cloudevents/types",
|
||||
"v2",
|
||||
"v2/binding",
|
||||
"v2/binding/format",
|
||||
"v2/binding/spec",
|
||||
"v2/client",
|
||||
"v2/context",
|
||||
"v2/event",
|
||||
"v2/event/datacodec",
|
||||
"v2/event/datacodec/json",
|
||||
"v2/event/datacodec/text",
|
||||
"v2/event/datacodec/xml",
|
||||
"v2/extensions",
|
||||
"v2/observability",
|
||||
"v2/protocol",
|
||||
"v2/protocol/http",
|
||||
"v2/types",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "2fa4bb1fbb4aac4d906b0173a2a408f701439b82"
|
||||
version = "v0.10.0"
|
||||
revision = "6dc020a8df7f3ee38d729e53cde2193ea7edf12a"
|
||||
version = "v2.0.0-preview8"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7a6852b35eb5bbc184561443762d225116ae630c26a7c4d90546619f1e7d2ad2"
|
||||
|
|
@ -143,6 +147,14 @@
|
|||
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
|
||||
version = "v1.6.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7fae9ec96d10b2afce0da23c378c8b3389319b7f92fa092f2621bba3078cfb4b"
|
||||
name = "github.com/hashicorp/golang-lru"
|
||||
packages = ["simplelru"]
|
||||
pruneopts = "NUT"
|
||||
revision = "14eae340515388ca95aa8e7b86f0de668e981f54"
|
||||
version = "v0.5.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:08c58ac78a8c1f61e9a96350066d30fe194b8779799bd932a79932a5166a173f"
|
||||
name = "github.com/kelseyhightower/envconfig"
|
||||
|
|
@ -163,6 +175,17 @@
|
|||
pruneopts = "NUT"
|
||||
revision = "e8a4306a5d37d2ea18705dbd592ecf1fa9264191"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a12508addb76a16593d8a3cee41d782fdf738f727d421f0bcc2dd2ee76821c01"
|
||||
name = "github.com/lightstep/tracecontext.go"
|
||||
packages = [
|
||||
"traceparent",
|
||||
"tracestate",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "1757c391b1acf4147823503f13e003115ea4e5df"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6"
|
||||
name = "github.com/matttproud/golang_protobuf_extensions"
|
||||
|
|
@ -186,6 +209,14 @@
|
|||
revision = "f197ec29e729f226d23370ea60f0e49b8f44ccf4"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4047c378584616813d610c9f993bf90dd0d07aed8d94bd3bc299cd35ececdcba"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "614d223910a179a466c1767a985424175c39b465"
|
||||
version = "v0.9.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:03bca087b180bf24c4f9060775f137775550a0834e18f0bca0520a868679dbd7"
|
||||
name = "github.com/prometheus/client_golang"
|
||||
|
|
@ -247,7 +278,7 @@
|
|||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bb38c0571e5ffeb394f2a7e4056fa5a7a6ea1acabb7fe71976340719fd104d02"
|
||||
digest = "1:2fe273976b8123b7fcd5d49a7ecbf340b92f370a727bed427ded821f68584d63"
|
||||
name = "go.opencensus.io"
|
||||
packages = [
|
||||
".",
|
||||
|
|
@ -255,8 +286,12 @@
|
|||
"exporter/zipkin",
|
||||
"internal",
|
||||
"internal/tagencoding",
|
||||
"metric/metricdata",
|
||||
"metric/metricproducer",
|
||||
"plugin/ochttp",
|
||||
"plugin/ochttp/propagation/b3",
|
||||
"plugin/ochttp/propagation/tracecontext",
|
||||
"resource",
|
||||
"stats",
|
||||
"stats/internal",
|
||||
"stats/view",
|
||||
|
|
@ -264,10 +299,11 @@
|
|||
"trace",
|
||||
"trace/internal",
|
||||
"trace/propagation",
|
||||
"trace/tracestate",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "e262766cd0d230a1bb7c37281e345e465f19b41b"
|
||||
version = "v0.14.0"
|
||||
revision = "75c0cca22312e51bfd4fafdbe9197ae399e18b38"
|
||||
version = "v0.20.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cc9d86ec4e6e3bdf87e3a421273bfeed003cf8e21351c0302fe8b0eb7b10efe6"
|
||||
|
|
@ -491,7 +527,7 @@
|
|||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"cloud.google.com/go/storage",
|
||||
"github.com/cloudevents/sdk-go",
|
||||
"github.com/cloudevents/sdk-go/v2",
|
||||
"github.com/dgrijalva/jwt-go",
|
||||
"github.com/eclipse/paho.mqtt.golang",
|
||||
"github.com/golang/protobuf/proto",
|
||||
|
|
|
|||
|
|
@ -14,3 +14,7 @@ required = [
|
|||
[[prune.project]]
|
||||
name = "knative.dev/test-infra"
|
||||
non-go = false
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/cloudevents/sdk-go"
|
||||
version = "v2.0.0-RC1"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
A simple web app written in Go that you can use to test knative eventing. It shows how to consume a [CloudEvent](https://cloudevents.io/) in Knative eventing, and optionally how to respond back with another CloudEvent in the http response, using the [Go SDK for CloudEvents](https://github.com/cloudevents/sdk-go)
|
||||
A simple web app written in Go that you can use to test knative eventing. It
|
||||
shows how to consume a [CloudEvent](https://cloudevents.io/) in Knative
|
||||
eventing, and optionally how to respond back with another CloudEvent in the http
|
||||
response, using the
|
||||
[Go SDK for CloudEvents](https://github.com/cloudevents/sdk-go)
|
||||
|
||||
We will deploy the app as a [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) along with a [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/).
|
||||
However, you can also deploy the app as a [Knative Serving Service](../../../../serving/README.md).
|
||||
We will deploy the app as a
|
||||
[Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
|
||||
along with a
|
||||
[Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/).
|
||||
However, you can also deploy the app as a
|
||||
[Knative Serving Service](../../../../serving/README.md).
|
||||
|
||||
Follow the steps below to create the sample code and then deploy the app to your
|
||||
cluster. You can also download a working copy of the sample, by running the
|
||||
|
|
@ -14,7 +22,9 @@ cd knative-docs/docs/eventing/samples/helloworld/helloworld-go
|
|||
|
||||
## Before you begin
|
||||
|
||||
- A Kubernetes cluster with [Knative Eventing](../../../getting-started.md#installing-knative-eventing) installed.
|
||||
- A Kubernetes cluster with
|
||||
[Knative Eventing](../../../getting-started.md#installing-knative-eventing)
|
||||
installed.
|
||||
- [Docker](https://www.docker.com) installed and running on your local machine,
|
||||
and a Docker Hub account configured (we'll use it for a container registry).
|
||||
|
||||
|
|
@ -24,33 +34,24 @@ cd knative-docs/docs/eventing/samples/helloworld/helloworld-go
|
|||
code creates a basic web server which listens on port 8080:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
cloudevents "github.com/cloudevents/sdk-go"
|
||||
cloudevents "github.com/cloudevents/sdk-go/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type eventData struct {
|
||||
Message string `json:"message,omitempty,string"`
|
||||
}
|
||||
|
||||
func receive(ctx context.Context, event cloudevents.Event, response *cloudevents.EventResponse) error {
|
||||
func receive(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) {
|
||||
// Here is where your code to process the event will go.
|
||||
// In this example we will log the event msg
|
||||
log.Printf("Event Context: %+v\n", event.Context)
|
||||
log.Printf("Event received. \n%s\n", event)
|
||||
data := &HelloWorld{}
|
||||
if err := event.DataAs(data); err != nil {
|
||||
log.Printf("Error while extracting cloudevent Data: %s\n", err.Error())
|
||||
return err
|
||||
return nil, cloudevents.NewHTTPResult(400, "failed to convert data: %s", err)
|
||||
}
|
||||
log.Printf("Hello World Message %q", data.Msg)
|
||||
log.Printf("Hello World Message from received event %q", data.Msg)
|
||||
|
||||
// Respond with another event (optional)
|
||||
// This is optional and is intended to show how to respond back with another event after processing.
|
||||
|
|
@ -59,21 +60,11 @@ cd knative-docs/docs/eventing/samples/helloworld/helloworld-go
|
|||
newEvent.SetID(uuid.New().String())
|
||||
newEvent.SetSource("knative/eventing/samples/hello-world")
|
||||
newEvent.SetType("dev.knative.samples.hifromknative")
|
||||
newEvent.SetData(HiFromKnative{Msg: "Hi from Knative!"})
|
||||
response.RespondWith(200, &newEvent)
|
||||
|
||||
log.Printf("Responded with event %v", newEvent)
|
||||
|
||||
return nil
|
||||
if err := newEvent.SetData(cloudevents.ApplicationJSON, HiFromKnative{Msg: "Hi from helloworld-go app!"}); err != nil {
|
||||
return nil, cloudevents.NewHTTPResult(500, "failed to set response data: %s", err)
|
||||
}
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Print("Hello world received a request.")
|
||||
target := os.Getenv("TARGET")
|
||||
if target == "" {
|
||||
target = "World"
|
||||
}
|
||||
fmt.Fprintf(w, "Hello %s!\n", target)
|
||||
log.Printf("Responding with event\n%s\n", newEvent)
|
||||
return &newEvent, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
@ -85,7 +76,9 @@ cd knative-docs/docs/eventing/samples/helloworld/helloworld-go
|
|||
log.Fatal(c.StartReceiver(context.Background(), receive))
|
||||
}
|
||||
```
|
||||
1. Create a new file named `eventschemas.go` and paste the following code. This defines the data schema of the CloudEvents.
|
||||
|
||||
1. Create a new file named `eventschemas.go` and paste the following code. This
|
||||
defines the data schema of the CloudEvents.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
|
@ -134,9 +127,9 @@ cd knative-docs/docs/eventing/samples/helloworld/helloworld-go
|
|||
CMD ["/helloworld"]
|
||||
```
|
||||
|
||||
1. Create a new file, `sample-app.yaml` and copy the following service definition
|
||||
into the file. Make sure to replace `{username}` with your Docker Hub
|
||||
username.
|
||||
1. Create a new file, `sample-app.yaml` and copy the following service
|
||||
definition into the file. Make sure to replace `{username}` with your Docker
|
||||
Hub username.
|
||||
|
||||
```yaml
|
||||
# Namespace for sample application with eventing enabled
|
||||
|
|
@ -218,61 +211,80 @@ folder) you're ready to build and deploy the sample app.
|
|||
```
|
||||
|
||||
1. After the build has completed and the container is pushed to docker hub, you
|
||||
can deploy the sample application into your cluster. Ensure that the container image value
|
||||
in `sample-app.yaml` matches the container you built in the previous step. Apply
|
||||
the configuration using `kubectl`:
|
||||
can deploy the sample application into your cluster. Ensure that the
|
||||
container image value in `sample-app.yaml` matches the container you built in
|
||||
the previous step. Apply the configuration using `kubectl`:
|
||||
|
||||
```shell
|
||||
kubectl apply --filename sample-app.yaml
|
||||
```
|
||||
1. Above command created a namespace `knative-samples` and labelled it with `knative-eventing-injection=enabled`, to enable eventing in the namespace. Verify using the following command:
|
||||
|
||||
1. Above command created a namespace `knative-samples` and labelled it with
|
||||
`knative-eventing-injection=enabled`, to enable eventing in the namespace.
|
||||
Verify using the following command:
|
||||
|
||||
```shell
|
||||
kubectl get ns knative-samples --show-labels
|
||||
```
|
||||
1. It deployed the helloworld-go app as a K8s Deployment and created a K8s service names helloworld-go. Verify using the following command.
|
||||
|
||||
1. It deployed the helloworld-go app as a K8s Deployment and created a K8s
|
||||
service names helloworld-go. Verify using the following command.
|
||||
|
||||
```shell
|
||||
kubectl --namespace knative-samples get deployments helloworld-go
|
||||
|
||||
kubectl --namespace knative-samples get svc helloworld-go
|
||||
```
|
||||
1. It created a Knative Eventing Trigger to route certain events to the helloworld-go application. Make sure that Ready=true
|
||||
|
||||
1. It created a Knative Eventing Trigger to route certain events to the
|
||||
helloworld-go application. Make sure that Ready=true
|
||||
```shell
|
||||
kubectl --namespace knative-samples get trigger helloworld-go
|
||||
```
|
||||
|
||||
## Send and verify CloudEvents
|
||||
Once you have deployed the application and verified that the namespace, sample application and trigger are ready, let's send a CloudEvent.
|
||||
|
||||
Once you have deployed the application and verified that the namespace, sample
|
||||
application and trigger are ready, let's send a CloudEvent.
|
||||
|
||||
### Send CloudEvent to the Broker
|
||||
We can send an http request directly to the [Broker](../../../broker-trigger.md) with correct CloudEvent headers set.
|
||||
|
||||
We can send an http request directly to the [Broker](../../../broker-trigger.md)
|
||||
with correct CloudEvent headers set.
|
||||
|
||||
1. Deploy a curl pod and SSH into it
|
||||
```shell
|
||||
kubectl --namespace knative-samples run curl --image=radial/busyboxplus:curl -it
|
||||
```
|
||||
1. Run the following in the SSH terminal
|
||||
|
||||
```shell
|
||||
curl -v "default-broker.knative-samples.svc.cluster.local" \
|
||||
-X POST \
|
||||
-H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f79" \
|
||||
-H "Ce-specversion: 0.3" \
|
||||
-H "Ce-Specversion: 1.0" \
|
||||
-H "Ce-Type: dev.knative.samples.helloworld" \
|
||||
-H "Ce-Source: dev.knative.samples/helloworldsource" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"msg":"Hello World from the curl pod."}'
|
||||
|
||||
|
||||
exit
|
||||
```
|
||||
|
||||
### Verify that event is received by helloworld-go app
|
||||
Helloworld-go app logs the context and the msg of the above event, and replies back with another event.
|
||||
|
||||
Helloworld-go app logs the context and the msg of the above event, and replies
|
||||
back with another event.
|
||||
|
||||
1. Display helloworld-go app logs
|
||||
```shell
|
||||
kubectl --namespace knative-samples logs -l app=helloworld-go --tail=50
|
||||
```
|
||||
`shell kubectl --namespace knative-samples logs -l app=helloworld-go --tail=50`
|
||||
You should see something similar to:
|
||||
|
||||
```shell
|
||||
Event received. Context: Context Attributes,
|
||||
specversion: 0.3
|
||||
Event received.
|
||||
Validation: valid
|
||||
Context Attributes,
|
||||
specversion: 1.0
|
||||
type: dev.knative.samples.helloworld
|
||||
source: dev.knative.samples/helloworldsource
|
||||
id: 536808d3-88be-4077-9d7a-a3f162705f79
|
||||
|
|
@ -282,24 +294,36 @@ Helloworld-go app logs the context and the msg of the above event, and replies b
|
|||
knativearrivaltime: 2019-10-04T22:35:26Z
|
||||
knativehistory: default-kn2-trigger-kn-channel.knative-samples.svc.cluster.local
|
||||
traceparent: 00-971d4644229653483d38c46e92a959c7-92c66312e4bb39be-00
|
||||
Data,
|
||||
{"msg":"Hello World from the curl pod."}
|
||||
|
||||
Hello World Message "Hello World from the curl pod."
|
||||
Responded with event Validation: valid
|
||||
Responded with event
|
||||
Validation: valid
|
||||
Context Attributes,
|
||||
specversion: 0.2
|
||||
specversion: 1.0
|
||||
type: dev.knative.samples.hifromknative
|
||||
source: knative/eventing/samples/hello-world
|
||||
id: 37458d77-01f5-411e-a243-a459bbf79682
|
||||
datacontenttype: application/json
|
||||
Data,
|
||||
{"msg":"Hi from Knative!"}
|
||||
|
||||
```
|
||||
Play around with the CloudEvent attributes in the curl command and the trigger specification to understand how [Triggers work](../../../broker-trigger.md#trigger).
|
||||
|
||||
Play around with the CloudEvent attributes in the curl command and the
|
||||
trigger specification to understand how
|
||||
[Triggers work](../../../broker-trigger.md#trigger).
|
||||
|
||||
## Verify reply from helloworld-go app
|
||||
`helloworld-go` app replies back with an event of `type= dev.knative.samples.hifromknative`, and `source=knative/eventing/samples/hello-world`. This event enters the eventing mesh via the Broker and can be delivered to other services using a Trigger
|
||||
|
||||
`helloworld-go` app replies back with an event of
|
||||
`type= dev.knative.samples.hifromknative`, and
|
||||
`source=knative/eventing/samples/hello-world`. This event enters the eventing
|
||||
mesh via the Broker and can be delivered to other services using a Trigger
|
||||
|
||||
1. Deploy a pod that receives any CloudEvent and logs the event to its output.
|
||||
|
||||
```shell
|
||||
kubectl --namespace knative-samples apply --filename - << END
|
||||
# event-display app deploment
|
||||
|
|
@ -339,6 +363,7 @@ Helloworld-go app logs the context and the msg of the above event, and replies b
|
|||
```
|
||||
|
||||
1. Create a trigger to deliver the event to the above service
|
||||
|
||||
```shell
|
||||
kubectl --namespace knative-samples apply --filename - << END
|
||||
apiVersion: eventing.knative.dev/v1alpha1
|
||||
|
|
@ -389,8 +414,6 @@ Helloworld-go app logs the context and the msg of the above event, and replies b
|
|||
|
||||
**Note: You could use the above approach to test your applications too.**
|
||||
|
||||
|
||||
|
||||
## Removing the sample app deployment
|
||||
|
||||
To remove the sample app from your cluster, delete the service record:
|
||||
|
|
|
|||
|
|
@ -2,27 +2,20 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
cloudevents "github.com/cloudevents/sdk-go"
|
||||
cloudevents "github.com/cloudevents/sdk-go/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type eventData struct {
|
||||
Message string `json:"message,omitempty,string"`
|
||||
}
|
||||
|
||||
func receive(ctx context.Context, event cloudevents.Event, response *cloudevents.EventResponse) error {
|
||||
func receive(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) {
|
||||
// Here is where your code to process the event will go.
|
||||
// In this example we will log the event msg
|
||||
log.Printf("Event received. Context: %v\n", event.Context)
|
||||
log.Printf("Event received. \n%s\n", event)
|
||||
data := &HelloWorld{}
|
||||
if err := event.DataAs(data); err != nil {
|
||||
log.Printf("Error while extracting cloudevent Data: %s\n", err.Error())
|
||||
return err
|
||||
return nil, cloudevents.NewHTTPResult(400, "failed to convert data: %s", err)
|
||||
}
|
||||
log.Printf("Hello World Message from received event %q", data.Msg)
|
||||
|
||||
|
|
@ -33,21 +26,11 @@ func receive(ctx context.Context, event cloudevents.Event, response *cloudevents
|
|||
newEvent.SetID(uuid.New().String())
|
||||
newEvent.SetSource("knative/eventing/samples/hello-world")
|
||||
newEvent.SetType("dev.knative.samples.hifromknative")
|
||||
newEvent.SetData(HiFromKnative{Msg: "Hi from helloworld-go app!"})
|
||||
response.RespondWith(200, &newEvent)
|
||||
|
||||
log.Printf("Responded with event %v", newEvent)
|
||||
|
||||
return nil
|
||||
if err := newEvent.SetData(cloudevents.ApplicationJSON, HiFromKnative{Msg: "Hi from helloworld-go app!"}); err != nil {
|
||||
return nil, cloudevents.NewHTTPResult(500, "failed to set response data: %s", err)
|
||||
}
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Print("Hello world received a request.")
|
||||
target := os.Getenv("TARGET")
|
||||
if target == "" {
|
||||
target = "World"
|
||||
}
|
||||
fmt.Fprintf(w, "Hello %s!\n", target)
|
||||
log.Printf("Responding with event\n%s\n", newEvent)
|
||||
return &newEvent, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ metadata:
|
|||
labels:
|
||||
knative-eventing-injection: enabled
|
||||
---
|
||||
# Helloworld-go app deploment
|
||||
# Helloworld-go app deployment
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
|
|
@ -41,7 +41,7 @@ spec:
|
|||
targetPort: 8080
|
||||
---
|
||||
# Knative Eventing Trigger to trigger the helloworld-go service
|
||||
apiVersion: eventing.knative.dev/v1alpha1
|
||||
apiVersion: eventing.knative.dev/v1beta1
|
||||
kind: Trigger
|
||||
metadata:
|
||||
name: helloworld-go
|
||||
|
|
|
|||
|
|
@ -7,16 +7,18 @@ type: "docs"
|
|||
|
||||
A simple web app written in Go that can receive and send Cloud Events that you
|
||||
can use for testing. It supports running in two modes:
|
||||
1. The default mode has the app reply to your input events with the output
|
||||
event, which is simplest for demonstrating things working in isolation,
|
||||
but is also the model for working for the Knative Eventing `Broker` concept.
|
||||
|
||||
2. `K_SINK` mode has the app send events to the destination encoded in `$K_SINK`,
|
||||
which is useful to demonstrate how folks can synthesize events to send to
|
||||
a Service or Broker when not initiated by a Broker invocation (e.g.
|
||||
1. The default mode has the app reply to your input events with the output
|
||||
event, which is simplest for demonstrating things working in isolation, but
|
||||
is also the model for working for the Knative Eventing `Broker` concept.
|
||||
|
||||
2. `K_SINK` mode has the app send events to the destination encoded in
|
||||
`$K_SINK`, which is useful to demonstrate how folks can synthesize events to
|
||||
send to a Service or Broker when not initiated by a Broker invocation (e.g.
|
||||
implementing an event source)
|
||||
|
||||
The application will use `$K_SINK`-mode whenever the environment variable is specified.
|
||||
The application will use `$K_SINK`-mode whenever the environment variable is
|
||||
specified.
|
||||
|
||||
Follow the steps below to create the sample code and then deploy the app to your
|
||||
cluster. You can also download a working copy of the sample, by running the
|
||||
|
|
@ -41,20 +43,20 @@ cd knative-docs/docs/serving/samples/cloudevents/cloudevents-go
|
|||
different modes of operation:
|
||||
|
||||
```go
|
||||
func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Event) error {
|
||||
func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Event) cloudevents.Result {
|
||||
// This is called whenever an event is received if $K_SINK is set, and sends a new event
|
||||
// to the url in $K_SINK.
|
||||
}
|
||||
|
||||
func (recv *Receiver) ReceiveAndReply(ctx context.Context, event cloudevents.Event, eventResp *cloudevents.EventResponse) error {
|
||||
func (recv *Receiver) ReceiveAndReply(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) {
|
||||
// This is called whenever an event is received if $K_SINK is NOT set, and it replies with
|
||||
// the new event instead.
|
||||
}
|
||||
```
|
||||
|
||||
1. If you look in `Dockerfile`, you will see a method for pulling in the dependencies and
|
||||
building a small Go container based on Alpine. You can build and push this to your
|
||||
registry of choice via:
|
||||
1. If you look in `Dockerfile`, you will see a method for pulling in the
|
||||
dependencies and building a small Go container based on Alpine. You can build
|
||||
and push this to your registry of choice via:
|
||||
|
||||
```shell
|
||||
docker build -t <image> .
|
||||
|
|
@ -110,7 +112,6 @@ You will get back:
|
|||
{"message":"Hello, Dave"}
|
||||
```
|
||||
|
||||
|
||||
## Removing the sample app deployment
|
||||
|
||||
To remove the sample app from your cluster, delete the service record:
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
cloudevents "github.com/cloudevents/sdk-go"
|
||||
cloudevents "github.com/cloudevents/sdk-go/v2"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
)
|
||||
|
||||
|
|
@ -55,16 +54,15 @@ type Response struct {
|
|||
}
|
||||
|
||||
// handle shared the logic for producing the Response event from the Request.
|
||||
func handle(req Request) (resp Response) {
|
||||
resp.Message = fmt.Sprintf("Hello, %s", req.Name)
|
||||
return
|
||||
func handle(req Request) Response {
|
||||
return Response{Message: fmt.Sprintf("Hello, %s", req.Name)}
|
||||
}
|
||||
|
||||
// ReceiveAndSend is invoked whenever we receive an event.
|
||||
func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Event) error {
|
||||
func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Event) cloudevents.Result {
|
||||
req := Request{}
|
||||
if err := event.DataAs(&req); err != nil {
|
||||
return err
|
||||
return cloudevents.NewHTTPResult(400, "failed to convert data: %s", err)
|
||||
}
|
||||
log.Printf("Got an event from: %q", req.Name)
|
||||
|
||||
|
|
@ -74,19 +72,19 @@ func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Even
|
|||
r := cloudevents.NewEvent(cloudevents.VersionV1)
|
||||
r.SetType("dev.knative.docs.sample")
|
||||
r.SetSource("https://github.com/knative/docs/docs/serving/samples/cloudevents/cloudevents-go")
|
||||
r.SetDataContentType("application/json")
|
||||
r.SetData(resp)
|
||||
if err := r.SetData("application/json", resp); err != nil {
|
||||
return cloudevents.NewHTTPResult(500, "failed to set response data: %s", err)
|
||||
}
|
||||
|
||||
ctx = cloudevents.ContextWithTarget(ctx, recv.Target)
|
||||
_, _, err := recv.client.Send(ctx, r)
|
||||
return err
|
||||
return recv.client.Send(ctx, r)
|
||||
}
|
||||
|
||||
// ReceiveAndReply is invoked whenever we receive an event.
|
||||
func (recv *Receiver) ReceiveAndReply(ctx context.Context, event cloudevents.Event, eventResp *cloudevents.EventResponse) error {
|
||||
func (recv *Receiver) ReceiveAndReply(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, cloudevents.Result) {
|
||||
req := Request{}
|
||||
if err := event.DataAs(&req); err != nil {
|
||||
return err
|
||||
return nil, cloudevents.NewHTTPResult(400, "failed to convert data: %s", err)
|
||||
}
|
||||
log.Printf("Got an event from: %q", req.Name)
|
||||
|
||||
|
|
@ -96,10 +94,9 @@ func (recv *Receiver) ReceiveAndReply(ctx context.Context, event cloudevents.Eve
|
|||
r := cloudevents.NewEvent(cloudevents.VersionV1)
|
||||
r.SetType("dev.knative.docs.sample")
|
||||
r.SetSource("https://github.com/knative/docs/docs/serving/samples/cloudevents/cloudevents-go")
|
||||
r.SetDataContentType("application/json")
|
||||
r.SetData(resp)
|
||||
|
||||
eventResp.RespondWith(http.StatusOK, &r)
|
||||
|
||||
return nil
|
||||
if err := r.SetData("application/json", resp); err != nil {
|
||||
return nil, cloudevents.NewHTTPResult(500, "failed to set response data: %s", err)
|
||||
}
|
||||
|
||||
return &r, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
// 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/pkg/cloudevents"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/client"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/context"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// Client
|
||||
|
||||
type ClientOption client.Option
|
||||
type Client = client.Client
|
||||
type ConvertFn = client.ConvertFn
|
||||
|
||||
// Event
|
||||
|
||||
type Event = cloudevents.Event
|
||||
type EventResponse = cloudevents.EventResponse
|
||||
|
||||
// Context
|
||||
|
||||
type EventContext = cloudevents.EventContext
|
||||
type EventContextV1 = cloudevents.EventContextV1
|
||||
type EventContextV01 = cloudevents.EventContextV01
|
||||
type EventContextV02 = cloudevents.EventContextV02
|
||||
type EventContextV03 = cloudevents.EventContextV03
|
||||
|
||||
// Custom Types
|
||||
|
||||
type Timestamp = types.Timestamp
|
||||
type URLRef = types.URLRef
|
||||
|
||||
// HTTP Transport
|
||||
|
||||
type HTTPOption http.Option
|
||||
type HTTPTransport = http.Transport
|
||||
type HTTPTransportContext = http.TransportContext
|
||||
type HTTPTransportResponseContext = http.TransportResponseContext
|
||||
type HTTPEncoding = http.Encoding
|
||||
|
||||
const (
|
||||
// Encoding
|
||||
|
||||
ApplicationXML = cloudevents.ApplicationXML
|
||||
ApplicationJSON = cloudevents.ApplicationJSON
|
||||
ApplicationCloudEventsJSON = cloudevents.ApplicationCloudEventsJSON
|
||||
ApplicationCloudEventsBatchJSON = cloudevents.ApplicationCloudEventsBatchJSON
|
||||
Base64 = cloudevents.Base64
|
||||
|
||||
// Event Versions
|
||||
|
||||
VersionV1 = cloudevents.CloudEventsVersionV1
|
||||
VersionV01 = cloudevents.CloudEventsVersionV01
|
||||
VersionV02 = cloudevents.CloudEventsVersionV02
|
||||
VersionV03 = cloudevents.CloudEventsVersionV03
|
||||
|
||||
// HTTP Transport Encodings
|
||||
|
||||
HTTPBinaryV1 = http.BinaryV1
|
||||
HTTPStructuredV1 = http.StructuredV1
|
||||
HTTPBatchedV1 = http.BatchedV1
|
||||
HTTPBinaryV01 = http.BinaryV01
|
||||
HTTPStructuredV01 = http.StructuredV01
|
||||
HTTPBinaryV02 = http.BinaryV02
|
||||
HTTPStructuredV02 = http.StructuredV02
|
||||
HTTPBinaryV03 = http.BinaryV03
|
||||
HTTPStructuredV03 = http.StructuredV03
|
||||
HTTPBatchedV03 = http.BatchedV03
|
||||
|
||||
// Context HTTP Transport Encodings
|
||||
|
||||
Binary = http.Binary
|
||||
Structured = http.Structured
|
||||
)
|
||||
|
||||
var (
|
||||
// ContentType Helpers
|
||||
|
||||
StringOfApplicationJSON = cloudevents.StringOfApplicationJSON
|
||||
StringOfApplicationXML = cloudevents.StringOfApplicationXML
|
||||
StringOfApplicationCloudEventsJSON = cloudevents.StringOfApplicationCloudEventsJSON
|
||||
StringOfApplicationCloudEventsBatchJSON = cloudevents.StringOfApplicationCloudEventsBatchJSON
|
||||
StringOfBase64 = cloudevents.StringOfBase64
|
||||
|
||||
// Client Creation
|
||||
|
||||
NewClient = client.New
|
||||
NewDefaultClient = client.NewDefault
|
||||
|
||||
// Client Options
|
||||
|
||||
WithEventDefaulter = client.WithEventDefaulter
|
||||
WithUUIDs = client.WithUUIDs
|
||||
WithTimeNow = client.WithTimeNow
|
||||
WithConverterFn = client.WithConverterFn
|
||||
|
||||
// Event Creation
|
||||
|
||||
NewEvent = cloudevents.New
|
||||
|
||||
// Tracing
|
||||
|
||||
EnableTracing = observability.EnableTracing
|
||||
|
||||
// Context
|
||||
|
||||
ContextWithTarget = context.WithTarget
|
||||
TargetFromContext = context.TargetFrom
|
||||
ContextWithEncoding = context.WithEncoding
|
||||
EncodingFromContext = context.EncodingFrom
|
||||
|
||||
// Custom Types
|
||||
|
||||
ParseTimestamp = types.ParseTimestamp
|
||||
ParseURLRef = types.ParseURLRef
|
||||
ParseURIRef = types.ParseURIRef
|
||||
ParseURI = types.ParseURI
|
||||
|
||||
// HTTP Transport
|
||||
|
||||
NewHTTPTransport = http.New
|
||||
|
||||
// HTTP Transport Options
|
||||
|
||||
WithTarget = http.WithTarget
|
||||
WithMethod = http.WithMethod
|
||||
WitHHeader = http.WithHeader
|
||||
WithShutdownTimeout = http.WithShutdownTimeout
|
||||
WithEncoding = http.WithEncoding
|
||||
WithContextBasedEncoding = http.WithContextBasedEncoding
|
||||
WithBinaryEncoding = http.WithBinaryEncoding
|
||||
WithStructuredEncoding = http.WithStructuredEncoding
|
||||
WithPort = http.WithPort
|
||||
WithPath = http.WithPath
|
||||
WithMiddleware = http.WithMiddleware
|
||||
WithLongPollTarget = http.WithLongPollTarget
|
||||
WithListener = http.WithListener
|
||||
|
||||
// HTTP Context
|
||||
|
||||
HTTPTransportContextFrom = http.TransportContextFrom
|
||||
ContextWithHeader = http.ContextWithHeader
|
||||
)
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http"
|
||||
)
|
||||
|
||||
// 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 cloudevents.Event) (context.Context, *cloudevents.Event, error)
|
||||
|
||||
// StartReceiver will register the provided function for callback on receipt
|
||||
// of a cloudevent. It will also start the underlying transport as it has
|
||||
// been configured.
|
||||
// This call is blocking.
|
||||
// Valid fn signatures are:
|
||||
// * func()
|
||||
// * func() error
|
||||
// * func(context.Context)
|
||||
// * func(context.Context) error
|
||||
// * func(cloudevents.Event)
|
||||
// * func(cloudevents.Event) error
|
||||
// * func(context.Context, cloudevents.Event)
|
||||
// * func(context.Context, cloudevents.Event) error
|
||||
// * func(cloudevents.Event, *cloudevents.EventResponse)
|
||||
// * func(cloudevents.Event, *cloudevents.EventResponse) error
|
||||
// * func(context.Context, cloudevents.Event, *cloudevents.EventResponse)
|
||||
// * func(context.Context, cloudevents.Event, *cloudevents.EventResponse) error
|
||||
// Note: if fn returns an error, it is treated as a critical and
|
||||
// EventResponse will not be processed.
|
||||
StartReceiver(ctx context.Context, fn interface{}) error
|
||||
}
|
||||
|
||||
// New produces a new client with the provided transport object and applied
|
||||
// client options.
|
||||
func New(t transport.Transport, opts ...Option) (Client, error) {
|
||||
c := &ceClient{
|
||||
transport: t,
|
||||
}
|
||||
if err := c.applyOptions(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.SetReceiver(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewDefault provides the good defaults for the common case using an HTTP
|
||||
// Transport 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) {
|
||||
t, err := http.New(http.WithBinaryEncoding())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := New(t, WithTimeNow(), WithUUIDs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type ceClient struct {
|
||||
transport transport.Transport
|
||||
fn *receiverFn
|
||||
|
||||
convertFn ConvertFn
|
||||
|
||||
receiverMu sync.Mutex
|
||||
eventDefaulterFns []EventDefaulter
|
||||
}
|
||||
|
||||
// Send transmits the provided event on a preconfigured Transport.
|
||||
// Send returns a response event if there is a response or an error if there
|
||||
// was an an issue validating the outbound event or the transport returns an
|
||||
// error.
|
||||
func (c *ceClient) Send(ctx context.Context, event cloudevents.Event) (context.Context, *cloudevents.Event, error) {
|
||||
ctx, r := observability.NewReporter(ctx, reportSend)
|
||||
rctx, resp, err := c.obsSend(ctx, event)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return rctx, resp, err
|
||||
}
|
||||
|
||||
func (c *ceClient) obsSend(ctx context.Context, event cloudevents.Event) (context.Context, *cloudevents.Event, error) {
|
||||
// Confirm we have a transport set.
|
||||
if c.transport == nil {
|
||||
return ctx, nil, fmt.Errorf("client not ready, transport not initialized")
|
||||
}
|
||||
// Apply the defaulter chain to the incoming event.
|
||||
if len(c.eventDefaulterFns) > 0 {
|
||||
for _, fn := range c.eventDefaulterFns {
|
||||
event = fn(ctx, event)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the event conforms to the CloudEvents Spec.
|
||||
if err := event.Validate(); err != nil {
|
||||
return ctx, nil, err
|
||||
}
|
||||
// Send the event over the transport.
|
||||
return c.transport.Send(ctx, event)
|
||||
}
|
||||
|
||||
// Receive is called from from the transport on event delivery.
|
||||
func (c *ceClient) Receive(ctx context.Context, event cloudevents.Event, resp *cloudevents.EventResponse) error {
|
||||
ctx, r := observability.NewReporter(ctx, reportReceive)
|
||||
err := c.obsReceive(ctx, event, resp)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ceClient) obsReceive(ctx context.Context, event cloudevents.Event, resp *cloudevents.EventResponse) error {
|
||||
if c.fn != nil {
|
||||
ctx, rFn := observability.NewReporter(ctx, reportReceiveFn)
|
||||
err := c.fn.invoke(ctx, event, resp)
|
||||
if err != nil {
|
||||
rFn.Error()
|
||||
} else {
|
||||
rFn.OK()
|
||||
}
|
||||
|
||||
// Apply the defaulter chain to the outgoing event.
|
||||
if err == nil && resp != nil && resp.Event != nil && len(c.eventDefaulterFns) > 0 {
|
||||
for _, fn := range c.eventDefaulterFns {
|
||||
*resp.Event = fn(ctx, *resp.Event)
|
||||
}
|
||||
// Validate the event conforms to the CloudEvents Spec.
|
||||
if err := resp.Event.Validate(); err != nil {
|
||||
return fmt.Errorf("cloudevent validation failed on response event: %v", err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.transport == nil {
|
||||
return fmt.Errorf("client not ready, transport not initialized")
|
||||
}
|
||||
if c.fn != nil {
|
||||
return fmt.Errorf("client already has a receiver")
|
||||
}
|
||||
|
||||
if fn, err := receiver(fn); err != nil {
|
||||
return err
|
||||
} else {
|
||||
c.fn = fn
|
||||
}
|
||||
|
||||
defer func() {
|
||||
c.fn = nil
|
||||
}()
|
||||
|
||||
return c.transport.StartReceiver(ctx)
|
||||
}
|
||||
|
||||
func (c *ceClient) applyOptions(opts ...Option) error {
|
||||
for _, fn := range opts {
|
||||
if err := fn(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert implements transport Converter.Convert.
|
||||
func (c *ceClient) Convert(ctx context.Context, m transport.Message, err error) (*cloudevents.Event, error) {
|
||||
if c.convertFn != nil {
|
||||
return c.convertFn(ctx, m, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
||||
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 (
|
||||
reportSend observed = iota
|
||||
reportReceive
|
||||
reportReceiveFn
|
||||
)
|
||||
|
||||
// TraceName implements Observable.TraceName
|
||||
func (o observed) TraceName() string {
|
||||
switch o {
|
||||
case reportSend:
|
||||
return "client/send"
|
||||
case reportReceive:
|
||||
return "client/receive"
|
||||
case reportReceiveFn:
|
||||
return "client/receive/fn"
|
||||
default:
|
||||
return "client/unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
case reportSend:
|
||||
return "send"
|
||||
case reportReceive:
|
||||
return "receive"
|
||||
case reportReceiveFn:
|
||||
return "receive/fn"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (o observed) LatencyMs() *stats.Float64Measure {
|
||||
return LatencyMs
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Option is the function signature required to be considered an client.Option.
|
||||
type Option func(*ceClient) error
|
||||
|
||||
// WithEventDefaulter adds an event defaulter to the end of the defaulter chain.
|
||||
func WithEventDefaulter(fn EventDefaulter) Option {
|
||||
return func(c *ceClient) error {
|
||||
if fn == nil {
|
||||
return fmt.Errorf("client option was given an nil event defaulter")
|
||||
}
|
||||
c.eventDefaulterFns = append(c.eventDefaulterFns, fn)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUUIDs adds DefaultIDToUUIDIfNotSet event defaulter to the end of the
|
||||
// defaulter chain.
|
||||
func WithUUIDs() Option {
|
||||
return func(c *ceClient) error {
|
||||
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(c *ceClient) error {
|
||||
c.eventDefaulterFns = append(c.eventDefaulterFns, DefaultTimeToNowIfNotSet)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConverterFn defines the function the transport will use to delegate
|
||||
// conversion of non-decodable messages.
|
||||
func WithConverterFn(fn ConvertFn) Option {
|
||||
return func(c *ceClient) error {
|
||||
if fn == nil {
|
||||
return fmt.Errorf("client option was given an nil message converter")
|
||||
}
|
||||
if c.transport.HasConverter() {
|
||||
return fmt.Errorf("transport converter already set")
|
||||
}
|
||||
c.convertFn = fn
|
||||
c.transport.SetConverter(c)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
)
|
||||
|
||||
// Receive is the signature of a fn to be invoked for incoming cloudevents.
|
||||
// If fn returns an error, EventResponse will not be considered by the client or
|
||||
// or transport.
|
||||
// This is just an FYI:
|
||||
type ReceiveFull func(context.Context, cloudevents.Event, *cloudevents.EventResponse) error
|
||||
|
||||
type receiverFn struct {
|
||||
numIn int
|
||||
fnValue reflect.Value
|
||||
|
||||
hasContextIn bool
|
||||
hasEventIn bool
|
||||
hasEventResponseIn bool
|
||||
|
||||
hasErrorOut bool
|
||||
}
|
||||
|
||||
// ConvertFn defines the signature the client expects to enable conversion
|
||||
// delegation.
|
||||
type ConvertFn func(context.Context, transport.Message, error) (*cloudevents.Event, error)
|
||||
|
||||
const (
|
||||
inParamUsage = "expected a function taking either no parameters, one or more of (context.Context, cloudevents.Event, *cloudevents.EventResponse) ordered"
|
||||
outParamUsage = "expected a function returning either nothing or an error"
|
||||
)
|
||||
|
||||
var (
|
||||
contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
eventType = reflect.TypeOf((*cloudevents.Event)(nil)).Elem()
|
||||
eventResponseType = reflect.TypeOf((*cloudevents.EventResponse)(nil)) // want the ptr type
|
||||
errorType = reflect.TypeOf((*error)(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) error
|
||||
// * func(cloudevents.Event)
|
||||
// * func(cloudevents.Event) error
|
||||
// * func(context.Context, cloudevents.Event)
|
||||
// * func(context.Context, cloudevents.Event) error
|
||||
// * func(cloudevents.Event, *cloudevents.EventResponse)
|
||||
// * func(cloudevents.Event, *cloudevents.EventResponse) error
|
||||
// * func(context.Context, cloudevents.Event, *cloudevents.EventResponse)
|
||||
// * func(context.Context, cloudevents.Event, *cloudevents.EventResponse) error
|
||||
//
|
||||
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(),
|
||||
}
|
||||
if err := r.validate(fnType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *receiverFn) invoke(ctx context.Context, event cloudevents.Event, resp *cloudevents.EventResponse) error {
|
||||
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(event))
|
||||
}
|
||||
if r.hasEventResponseIn {
|
||||
args = append(args, reflect.ValueOf(resp))
|
||||
}
|
||||
}
|
||||
v := r.fnValue.Call(args)
|
||||
if r.hasErrorOut && len(v) >= 1 {
|
||||
if err, ok := v[0].Interface().(error); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verifies that the inputs to a function have a valid signature
|
||||
// Valid input is to be [0, all] of
|
||||
// context.Context, cloudevents.Event, *cloudevents.EventResponse in this order.
|
||||
func (r *receiverFn) validateInParamSignature(fnType reflect.Type) error {
|
||||
r.hasContextIn = false
|
||||
r.hasEventIn = false
|
||||
r.hasEventResponseIn = false
|
||||
|
||||
switch fnType.NumIn() {
|
||||
case 3:
|
||||
// has to be cloudevents.Event, *cloudevents.EventResponse
|
||||
if !fnType.In(2).ConvertibleTo(eventResponseType) {
|
||||
return fmt.Errorf("%s; cannot convert parameter 2 from %s to *cloudevents.EventResponse", inParamUsage, fnType.In(2))
|
||||
} else {
|
||||
r.hasEventResponseIn = true
|
||||
}
|
||||
fallthrough
|
||||
case 2:
|
||||
// can be cloudevents.Event or *cloudevents.EventResponse
|
||||
if !fnType.In(1).ConvertibleTo(eventResponseType) {
|
||||
if !fnType.In(1).ConvertibleTo(eventType) {
|
||||
return fmt.Errorf("%s; cannot convert parameter 1 from %s to cloudevents.Event or *cloudevents.EventResponse", inParamUsage, fnType.In(1))
|
||||
} else {
|
||||
r.hasEventIn = true
|
||||
}
|
||||
} else if r.hasEventResponseIn {
|
||||
return fmt.Errorf("%s; duplicate parameter of type *cloudevents.EventResponse", inParamUsage)
|
||||
} else {
|
||||
r.hasEventResponseIn = true
|
||||
}
|
||||
fallthrough
|
||||
case 1:
|
||||
if !fnType.In(0).ConvertibleTo(contextType) {
|
||||
if !fnType.In(0).ConvertibleTo(eventResponseType) {
|
||||
if !fnType.In(0).ConvertibleTo(eventType) {
|
||||
return fmt.Errorf("%s; cannot convert parameter 0 from %s to context.Context, cloudevents.Event or *cloudevents.EventResponse", inParamUsage, fnType.In(0))
|
||||
} else if r.hasEventIn {
|
||||
return fmt.Errorf("%s; duplicate parameter of type cloudevents.Event", inParamUsage)
|
||||
} else {
|
||||
r.hasEventIn = true
|
||||
}
|
||||
} else if r.hasEventResponseIn {
|
||||
return fmt.Errorf("%s; duplicate parameter of type *cloudevents.EventResponse", inParamUsage)
|
||||
} else if r.hasEventIn {
|
||||
return fmt.Errorf("%s; out of order parameter 0 for %s", inParamUsage, fnType.In(1))
|
||||
} else {
|
||||
r.hasEventResponseIn = 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:
|
||||
// (), (error)
|
||||
func (r *receiverFn) validateOutParamSignature(fnType reflect.Type) error {
|
||||
r.hasErrorOut = false
|
||||
switch fnType.NumOut() {
|
||||
case 1:
|
||||
paramNo := fnType.NumOut() - 1
|
||||
paramType := fnType.Out(paramNo)
|
||||
if !paramType.ConvertibleTo(errorType) {
|
||||
return fmt.Errorf("%s; cannot convert return type %d from %s to error", outParamUsage, paramNo, paramType)
|
||||
} else {
|
||||
r.hasErrorOut = 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,97 +0,0 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
)
|
||||
|
||||
// Decode takes `in` as []byte, or base64 string, normalizes in to unquoted and
|
||||
// base64 decoded []byte if required, and then attempts to use json.Unmarshal
|
||||
// to convert those bytes to `out`. Returns and error if this process fails.
|
||||
func Decode(ctx context.Context, in, out interface{}) error {
|
||||
_, r := observability.NewReporter(ctx, reportDecode)
|
||||
err := obsDecode(ctx, in, out)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func obsDecode(ctx context.Context, in, out interface{}) error {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
if out == nil {
|
||||
return fmt.Errorf("out is nil")
|
||||
}
|
||||
|
||||
b, ok := in.([]byte) // TODO: I think there is fancy marshaling happening here. Fix with reflection?
|
||||
if !ok {
|
||||
var err error
|
||||
b, err = json.Marshal(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[json] failed to marshal in: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: the spec says json could be just data... At the moment we expect wrapped.
|
||||
if len(b) > 1 && (b[0] == byte('"') || (b[0] == byte('\\') && b[1] == byte('"'))) {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return fmt.Errorf("[json] failed to unquote in: %s", err.Error())
|
||||
}
|
||||
if len(s) > 0 && (s[0] == '{' || s[0] == '[') {
|
||||
// looks like json, use it
|
||||
b = []byte(s)
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, out); err != nil {
|
||||
return fmt.Errorf("[json] found bytes \"%s\", but failed to unmarshal: %s", string(b), 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) {
|
||||
_, r := observability.NewReporter(ctx, reportEncode)
|
||||
b, err := obsEncode(ctx, in)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
|
||||
func obsEncode(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,90 +0,0 @@
|
|||
package xml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
)
|
||||
|
||||
// Decode takes `in` as []byte, or base64 string, normalizes in to unquoted and
|
||||
// base64 decoded []byte if required, and then attempts to use xml.Unmarshal
|
||||
// to convert those bytes to `out`. Returns and error if this process fails.
|
||||
func Decode(ctx context.Context, in, out interface{}) error {
|
||||
_, r := observability.NewReporter(ctx, reportDecode)
|
||||
err := obsDecode(ctx, in, out)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func obsDecode(ctx context.Context, in, out interface{}) error {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, ok := in.([]byte)
|
||||
if !ok {
|
||||
var err error
|
||||
b, err = xml.Marshal(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[xml] failed to marshal in: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// If the message is encoded as a base64 block as a string, we need to
|
||||
// decode that first before trying to unmarshal the bytes
|
||||
if len(b) > 1 && (b[0] == byte('"') || (b[0] == byte('\\') && b[1] == byte('"'))) {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return fmt.Errorf("[xml] failed to unquote quoted data: %s", err.Error())
|
||||
}
|
||||
if len(s) > 0 && s[0] == '<' {
|
||||
// looks like xml, use it
|
||||
b = []byte(s)
|
||||
} else if len(s) > 0 {
|
||||
// looks like base64, decode
|
||||
bs, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[xml] failed to decode base64 encoded string: %s", err.Error())
|
||||
}
|
||||
b = bs
|
||||
}
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(b, out); err != nil {
|
||||
return fmt.Errorf("[xml] found bytes, but failed to unmarshal: %s %s", err.Error(), string(b))
|
||||
}
|
||||
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) {
|
||||
_, r := observability.NewReporter(ctx, reportEncode)
|
||||
b, err := obsEncode(ctx, in)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
|
||||
func obsEncode(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,108 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Event represents the canonical representation of a CloudEvent.
|
||||
type Event struct {
|
||||
Context EventContext
|
||||
Data interface{}
|
||||
DataEncoded bool
|
||||
DataBinary bool
|
||||
}
|
||||
|
||||
const (
|
||||
defaultEventVersion = CloudEventsVersionV02
|
||||
)
|
||||
|
||||
// New returns a new Event, an optional version can be passed to change the
|
||||
// default spec version from 0.2 to the provided version.
|
||||
func New(version ...string) Event {
|
||||
specVersion := defaultEventVersion // TODO: should there be a default? or set a default?
|
||||
if len(version) >= 1 {
|
||||
specVersion = version[0]
|
||||
}
|
||||
e := &Event{}
|
||||
e.SetSpecVersion(specVersion)
|
||||
return *e
|
||||
}
|
||||
|
||||
// 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 err := e.Context.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: validate data.
|
||||
|
||||
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.Data != nil {
|
||||
b.WriteString("Data,\n ")
|
||||
if strings.HasPrefix(e.DataContentType(), ApplicationJSON) {
|
||||
var prettyJSON bytes.Buffer
|
||||
|
||||
data, ok := e.Data.([]byte)
|
||||
if !ok {
|
||||
var err error
|
||||
data, err = json.Marshal(e.Data)
|
||||
if err != nil {
|
||||
data = []byte(err.Error())
|
||||
}
|
||||
}
|
||||
err := json.Indent(&prettyJSON, data, " ", " ")
|
||||
if err != nil {
|
||||
b.Write(e.Data.([]byte))
|
||||
} else {
|
||||
b.Write(prettyJSON.Bytes())
|
||||
}
|
||||
} else {
|
||||
b.Write(e.Data.([]byte))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec"
|
||||
)
|
||||
|
||||
// Data is special. Break it out into it's own file.
|
||||
|
||||
// SetData implements EventWriter.SetData
|
||||
func (e *Event) SetData(obj interface{}) error {
|
||||
if e.SpecVersion() != CloudEventsVersionV1 {
|
||||
return e.legacySetData(obj)
|
||||
}
|
||||
|
||||
// Version 1.0 and above.
|
||||
|
||||
// TODO: we will have to be smarter about how data relates to media type.
|
||||
// but the issue is we can not just encode data anymore without understanding
|
||||
// what the encoding will be on the outbound event. Structured will use
|
||||
// data_base64, binary will not (if the transport supports binary mode).
|
||||
|
||||
// TODO: look at content encoding too.
|
||||
|
||||
switch obj.(type) {
|
||||
case []byte:
|
||||
e.Data = obj
|
||||
e.DataEncoded = true
|
||||
e.DataBinary = true
|
||||
default:
|
||||
data, err := datacodec.Encode(context.Background(), e.DataMediaType(), obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.Data = data
|
||||
e.DataEncoded = true
|
||||
e.DataBinary = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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.Data = string(buf)
|
||||
} else {
|
||||
e.Data = data
|
||||
}
|
||||
e.DataEncoded = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Event) DataBytes() ([]byte, error) {
|
||||
if !e.DataEncoded {
|
||||
if err := e.SetData(e.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
b, ok := e.Data.([]byte)
|
||||
if !ok {
|
||||
if s, ok := e.Data.(string); ok {
|
||||
b = []byte(s)
|
||||
} else {
|
||||
// No data.
|
||||
return []byte(nil), nil
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
const (
|
||||
quotes = `"'`
|
||||
)
|
||||
|
||||
// DataAs attempts to populate the provided data object with the event payload.
|
||||
// data should be a pointer type.
|
||||
func (e Event) DataAs(data interface{}) error { // TODO: Clean this function up
|
||||
if e.Data == nil {
|
||||
return nil
|
||||
}
|
||||
obj, ok := e.Data.([]byte)
|
||||
if !ok {
|
||||
if s, ok := e.Data.(string); ok {
|
||||
obj = []byte(s)
|
||||
} else {
|
||||
return errors.New("data was not a byte slice or string")
|
||||
}
|
||||
}
|
||||
if len(obj) == 0 {
|
||||
// No data.
|
||||
return nil
|
||||
}
|
||||
if e.Context.DeprecatedGetDataContentEncoding() == Base64 {
|
||||
var bs []byte
|
||||
// test to see if we need to unquote the data.
|
||||
if obj[0] == quotes[0] || obj[0] == quotes[1] {
|
||||
str, err := strconv.Unquote(string(obj))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bs = []byte(str)
|
||||
} else {
|
||||
bs = obj
|
||||
}
|
||||
|
||||
buf := make([]byte, base64.StdEncoding.DecodedLen(len(bs)))
|
||||
n, err := base64.StdEncoding.Decode(buf, bs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode data from base64: %s", err.Error())
|
||||
}
|
||||
obj = buf[:n]
|
||||
}
|
||||
|
||||
mediaType := ""
|
||||
if e.Context.GetDataContentType() != "" {
|
||||
var err error
|
||||
mediaType, err = e.Context.GetDataMediaType()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return datacodec.Decode(context.Background(), mediaType, obj, data)
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
// EventResponse represents the canonical representation of a Response to a
|
||||
// CloudEvent from a receiver. Response implementation is Transport dependent.
|
||||
type EventResponse struct {
|
||||
Status int
|
||||
Event *Event
|
||||
Reason string
|
||||
// Context is transport specific struct to allow for controlling transport
|
||||
// response details.
|
||||
// For example, see http.TransportResponseContext.
|
||||
Context interface{}
|
||||
}
|
||||
|
||||
// RespondWith sets up the instance of EventResponse to be set with status and
|
||||
// an event. Response implementation is Transport dependent.
|
||||
func (e *EventResponse) RespondWith(status int, event *Event) {
|
||||
if e == nil {
|
||||
// if nil, response not supported
|
||||
return
|
||||
}
|
||||
e.Status = status
|
||||
if event != nil {
|
||||
e.Event = event
|
||||
}
|
||||
}
|
||||
|
||||
// Error sets the instance of EventResponse to be set with an error code and
|
||||
// reason string. Response implementation is Transport dependent.
|
||||
func (e *EventResponse) Error(status int, reason string) {
|
||||
if e == nil {
|
||||
// if nil, response not supported
|
||||
return
|
||||
}
|
||||
e.Status = status
|
||||
e.Reason = reason
|
||||
}
|
||||
|
|
@ -1,272 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// CloudEventsVersionV01 represents the version 0.1 of the CloudEvents spec.
|
||||
CloudEventsVersionV01 = "0.1"
|
||||
)
|
||||
|
||||
// EventContextV01 holds standard metadata about an event. See
|
||||
// https://github.com/cloudevents/spec/blob/v0.1/spec.md#context-attributes for
|
||||
// details on these fields.
|
||||
type EventContextV01 struct {
|
||||
// The version of the CloudEvents specification used by the event.
|
||||
CloudEventsVersion string `json:"cloudEventsVersion,omitempty"`
|
||||
// ID of the event; must be non-empty and unique within the scope of the producer.
|
||||
EventID string `json:"eventID"`
|
||||
// Timestamp when the event happened.
|
||||
EventTime *types.Timestamp `json:"eventTime,omitempty"`
|
||||
// Type of occurrence which has happened.
|
||||
EventType string `json:"eventType"`
|
||||
// The version of the `eventType`; this is producer-specific.
|
||||
EventTypeVersion *string `json:"eventTypeVersion,omitempty"`
|
||||
// A link to the schema that the `data` attribute adheres to.
|
||||
SchemaURL *types.URLRef `json:"schemaURL,omitempty"`
|
||||
// A MIME (RFC 2046) string describing the media type of `data`.
|
||||
// TODO: Should an empty string assume `application/json`, or auto-detect the content?
|
||||
ContentType *string `json:"contentType,omitempty"`
|
||||
// A URI describing the event producer.
|
||||
Source types.URLRef `json:"source"`
|
||||
// Additional metadata without a well-defined structure.
|
||||
Extensions map[string]interface{} `json:"extensions,omitempty"`
|
||||
}
|
||||
|
||||
// Adhere to EventContext
|
||||
var _ EventContext = (*EventContextV01)(nil)
|
||||
|
||||
// ExtensionAs implements EventContextReader.ExtensionAs
|
||||
func (ec EventContextV01) ExtensionAs(name string, obj interface{}) error {
|
||||
value, ok := ec.Extensions[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("extension %q does not exist", name)
|
||||
}
|
||||
// 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 *EventContextV01) SetExtension(name string, value interface{}) error {
|
||||
if ec.Extensions == nil {
|
||||
ec.Extensions = make(map[string]interface{})
|
||||
}
|
||||
if value == nil {
|
||||
delete(ec.Extensions, name)
|
||||
} else {
|
||||
ec.Extensions[name] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone implements EventContextConverter.Clone
|
||||
func (ec EventContextV01) Clone() EventContext {
|
||||
return ec.AsV01()
|
||||
}
|
||||
|
||||
// AsV01 implements EventContextConverter.AsV01
|
||||
func (ec EventContextV01) AsV01() *EventContextV01 {
|
||||
ec.CloudEventsVersion = CloudEventsVersionV01
|
||||
return &ec
|
||||
}
|
||||
|
||||
// AsV02 implements EventContextConverter.AsV02
|
||||
func (ec EventContextV01) AsV02() *EventContextV02 {
|
||||
ret := EventContextV02{
|
||||
SpecVersion: CloudEventsVersionV02,
|
||||
Type: ec.EventType,
|
||||
Source: ec.Source,
|
||||
ID: ec.EventID,
|
||||
Time: ec.EventTime,
|
||||
SchemaURL: ec.SchemaURL,
|
||||
ContentType: ec.ContentType,
|
||||
Extensions: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// eventTypeVersion was retired in v0.2, so put it in an extension.
|
||||
if ec.EventTypeVersion != nil {
|
||||
_ = ret.SetExtension(EventTypeVersionKey, *ec.EventTypeVersion)
|
||||
}
|
||||
if ec.Extensions != nil {
|
||||
for k, v := range ec.Extensions {
|
||||
ret.Extensions[k] = v
|
||||
}
|
||||
}
|
||||
if len(ret.Extensions) == 0 {
|
||||
ret.Extensions = nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// AsV03 implements EventContextConverter.AsV03
|
||||
func (ec EventContextV01) AsV03() *EventContextV03 {
|
||||
return ec.AsV02().AsV03()
|
||||
}
|
||||
|
||||
// AsV1 implements EventContextConverter.AsV1
|
||||
func (ec EventContextV01) AsV1() *EventContextV1 {
|
||||
return ec.AsV02().AsV03().AsV1()
|
||||
}
|
||||
|
||||
// Validate returns errors based on requirements from the CloudEvents spec.
|
||||
// For more details, see https://github.com/cloudevents/spec/blob/v0.1/spec.md
|
||||
func (ec EventContextV01) Validate() error {
|
||||
errors := []string(nil)
|
||||
|
||||
// eventType
|
||||
// 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.EventType)
|
||||
if eventType == "" {
|
||||
errors = append(errors, "eventType: MUST be a non-empty string")
|
||||
}
|
||||
|
||||
// eventTypeVersion
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST be a non-empty string
|
||||
if ec.EventTypeVersion != nil {
|
||||
eventTypeVersion := strings.TrimSpace(*ec.EventTypeVersion)
|
||||
if eventTypeVersion == "" {
|
||||
errors = append(errors, "eventTypeVersion: if present, MUST be a non-empty string")
|
||||
}
|
||||
}
|
||||
|
||||
// cloudEventsVersion
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
cloudEventsVersion := strings.TrimSpace(ec.CloudEventsVersion)
|
||||
if cloudEventsVersion == "" {
|
||||
errors = append(errors, "cloudEventsVersion: MUST be a non-empty string")
|
||||
}
|
||||
|
||||
// source
|
||||
// Type: URI
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
source := strings.TrimSpace(ec.Source.String())
|
||||
if source == "" {
|
||||
errors = append(errors, "source: REQUIRED")
|
||||
}
|
||||
|
||||
// eventID
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
// MUST be unique within the scope of the producer
|
||||
eventID := strings.TrimSpace(ec.EventID)
|
||||
if eventID == "" {
|
||||
errors = append(errors, "eventID: MUST be a non-empty string")
|
||||
|
||||
// no way to test "MUST be unique within the scope of the producer"
|
||||
}
|
||||
|
||||
// eventTime
|
||||
// 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 eventTime 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")
|
||||
}
|
||||
}
|
||||
|
||||
// contentType
|
||||
// Type: String per RFC 2046
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 2046
|
||||
if ec.ContentType != nil {
|
||||
contentType := strings.TrimSpace(*ec.ContentType)
|
||||
if contentType == "" {
|
||||
// TODO: need to test for RFC 2046
|
||||
errors = append(errors, "contentType: if present, MUST adhere to the format specified in RFC 2046")
|
||||
}
|
||||
}
|
||||
|
||||
// extensions
|
||||
// Type: Map
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST contain at least one entry
|
||||
if ec.Extensions != nil {
|
||||
if len(ec.Extensions) == 0 {
|
||||
errors = append(errors, "extensions: if present, MUST contain at least one entry")
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf(strings.Join(errors, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a pretty-printed representation of the EventContext.
|
||||
func (ec EventContextV01) String() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
b.WriteString("Context Attributes,\n")
|
||||
|
||||
b.WriteString(" cloudEventsVersion: " + ec.CloudEventsVersion + "\n")
|
||||
b.WriteString(" eventType: " + ec.EventType + "\n")
|
||||
if ec.EventTypeVersion != nil {
|
||||
b.WriteString(" eventTypeVersion: " + *ec.EventTypeVersion + "\n")
|
||||
}
|
||||
b.WriteString(" source: " + ec.Source.String() + "\n")
|
||||
b.WriteString(" eventID: " + ec.EventID + "\n")
|
||||
if ec.EventTime != nil {
|
||||
b.WriteString(" eventTime: " + ec.EventTime.String() + "\n")
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
b.WriteString(" schemaURL: " + ec.SchemaURL.String() + "\n")
|
||||
}
|
||||
if ec.ContentType != nil {
|
||||
b.WriteString(" contentType: " + *ec.ContentType + "\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()
|
||||
}
|
||||
101
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_reader.go
generated
vendored
101
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_reader.go
generated
vendored
|
|
@ -1,101 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Adhere to EventContextReader
|
||||
var _ EventContextReader = (*EventContextV01)(nil)
|
||||
|
||||
// GetSpecVersion implements EventContextReader.GetSpecVersion
|
||||
func (ec EventContextV01) GetSpecVersion() string {
|
||||
if ec.CloudEventsVersion != "" {
|
||||
return ec.CloudEventsVersion
|
||||
}
|
||||
return CloudEventsVersionV01
|
||||
}
|
||||
|
||||
// GetDataContentType implements EventContextReader.GetDataContentType
|
||||
func (ec EventContextV01) GetDataContentType() string {
|
||||
if ec.ContentType != nil {
|
||||
return *ec.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetDataMediaType implements EventContextReader.GetDataMediaType
|
||||
func (ec EventContextV01) GetDataMediaType() (string, error) {
|
||||
if ec.ContentType != nil {
|
||||
mediaType, _, err := mime.ParseMediaType(*ec.ContentType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return mediaType, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetType implements EventContextReader.GetType
|
||||
func (ec EventContextV01) GetType() string {
|
||||
return ec.EventType
|
||||
}
|
||||
|
||||
// GetSource implements EventContextReader.GetSource
|
||||
func (ec EventContextV01) GetSource() string {
|
||||
return ec.Source.String()
|
||||
}
|
||||
|
||||
// GetSubject implements EventContextReader.GetSubject
|
||||
func (ec EventContextV01) GetSubject() string {
|
||||
var sub string
|
||||
if err := ec.ExtensionAs(SubjectKey, &sub); err != nil {
|
||||
return ""
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
// GetID implements EventContextReader.GetID
|
||||
func (ec EventContextV01) GetID() string {
|
||||
return ec.EventID
|
||||
}
|
||||
|
||||
// GetTime implements EventContextReader.GetTime
|
||||
func (ec EventContextV01) GetTime() time.Time {
|
||||
if ec.EventTime != nil {
|
||||
return ec.EventTime.Time
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// GetDataSchema implements EventContextReader.GetDataSchema
|
||||
func (ec EventContextV01) GetDataSchema() string {
|
||||
if ec.SchemaURL != nil {
|
||||
return ec.SchemaURL.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeprecatedGetDataContentEncoding implements EventContextReader.DeprecatedGetDataContentEncoding
|
||||
func (ec EventContextV01) DeprecatedGetDataContentEncoding() string {
|
||||
var enc string
|
||||
if err := ec.ExtensionAs(DataContentEncodingKey, &enc); err != nil {
|
||||
return ""
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
// GetExtensions implements EventContextReader.GetExtensions
|
||||
func (ec EventContextV01) GetExtensions() map[string]interface{} {
|
||||
return ec.Extensions
|
||||
}
|
||||
|
||||
// GetExtension implements EventContextReader.GetExtension
|
||||
func (ec EventContextV01) GetExtension(key string) (interface{}, error) {
|
||||
v, ok := caseInsensitiveSearch(key, ec.Extensions)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%q not found", key)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
104
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_writer.go
generated
vendored
104
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_writer.go
generated
vendored
|
|
@ -1,104 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// Adhere to EventContextWriter
|
||||
var _ EventContextWriter = (*EventContextV01)(nil)
|
||||
|
||||
// SetSpecVersion implements EventContextWriter.SetSpecVersion
|
||||
func (ec *EventContextV01) SetSpecVersion(v string) error {
|
||||
if v != CloudEventsVersionV01 {
|
||||
return fmt.Errorf("invalid version %q, expecting %q", v, CloudEventsVersionV01)
|
||||
}
|
||||
ec.CloudEventsVersion = CloudEventsVersionV01
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataContentType implements EventContextWriter.SetDataContentType
|
||||
func (ec *EventContextV01) SetDataContentType(ct string) error {
|
||||
ct = strings.TrimSpace(ct)
|
||||
if ct == "" {
|
||||
ec.ContentType = nil
|
||||
} else {
|
||||
ec.ContentType = &ct
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetType implements EventContextWriter.SetType
|
||||
func (ec *EventContextV01) SetType(t string) error {
|
||||
t = strings.TrimSpace(t)
|
||||
ec.EventType = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSource implements EventContextWriter.SetSource
|
||||
func (ec *EventContextV01) SetSource(u string) error {
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.Source = types.URLRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSubject implements EventContextWriter.SetSubject
|
||||
func (ec *EventContextV01) SetSubject(s string) error {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ec.SetExtension(SubjectKey, nil)
|
||||
}
|
||||
return ec.SetExtension(SubjectKey, s)
|
||||
}
|
||||
|
||||
// SetID implements EventContextWriter.SetID
|
||||
func (ec *EventContextV01) SetID(id string) error {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
return errors.New("event id is required to be a non-empty string")
|
||||
}
|
||||
ec.EventID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTime implements EventContextWriter.SetTime
|
||||
func (ec *EventContextV01) SetTime(t time.Time) error {
|
||||
if t.IsZero() {
|
||||
ec.EventTime = nil
|
||||
} else {
|
||||
ec.EventTime = &types.Timestamp{Time: t}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataSchema implements EventContextWriter.SetDataSchema
|
||||
func (ec *EventContextV01) 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.URLRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeprecatedSetDataContentEncoding implements EventContextWriter.DeprecatedSetDataContentEncoding
|
||||
func (ec *EventContextV01) DeprecatedSetDataContentEncoding(e string) error {
|
||||
e = strings.ToLower(strings.TrimSpace(e))
|
||||
if e == "" {
|
||||
return ec.SetExtension(DataContentEncodingKey, nil)
|
||||
}
|
||||
return ec.SetExtension(DataContentEncodingKey, e)
|
||||
}
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// CloudEventsVersionV02 represents the version 0.2 of the CloudEvents spec.
|
||||
CloudEventsVersionV02 = "0.2"
|
||||
)
|
||||
|
||||
// EventContextV02 represents the non-data attributes of a CloudEvents v0.2
|
||||
// event.
|
||||
type EventContextV02 struct {
|
||||
// The version of the CloudEvents specification used by the event.
|
||||
SpecVersion string `json:"specversion"`
|
||||
// The type of the occurrence which has happened.
|
||||
Type string `json:"type"`
|
||||
// A URI describing the event producer.
|
||||
Source types.URLRef `json:"source"`
|
||||
// ID of the event; must be non-empty and unique within the scope of the producer.
|
||||
ID string `json:"id"`
|
||||
// Timestamp when the event happened.
|
||||
Time *types.Timestamp `json:"time,omitempty"`
|
||||
// A link to the schema that the `data` attribute adheres to.
|
||||
SchemaURL *types.URLRef `json:"schemaurl,omitempty"`
|
||||
// A MIME (RFC2046) string describing the media type of `data`.
|
||||
// TODO: Should an empty string assume `application/json`, `application/octet-stream`, or auto-detect the content?
|
||||
ContentType *string `json:"contenttype,omitempty"`
|
||||
// Additional extension metadata beyond the base spec.
|
||||
Extensions map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// Adhere to EventContext
|
||||
var _ EventContext = (*EventContextV02)(nil)
|
||||
|
||||
// ExtensionAs implements EventContext.ExtensionAs
|
||||
func (ec EventContextV02) 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 *EventContextV02) SetExtension(name string, value interface{}) error {
|
||||
if ec.Extensions == nil {
|
||||
ec.Extensions = make(map[string]interface{})
|
||||
}
|
||||
if value == nil {
|
||||
delete(ec.Extensions, name)
|
||||
} else {
|
||||
ec.Extensions[name] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone implements EventContextConverter.Clone
|
||||
func (ec EventContextV02) Clone() EventContext {
|
||||
return ec.AsV02()
|
||||
}
|
||||
|
||||
// AsV01 implements EventContextConverter.AsV01
|
||||
func (ec EventContextV02) AsV01() *EventContextV01 {
|
||||
ret := EventContextV01{
|
||||
CloudEventsVersion: CloudEventsVersionV01,
|
||||
EventID: ec.ID,
|
||||
EventTime: ec.Time,
|
||||
EventType: ec.Type,
|
||||
SchemaURL: ec.SchemaURL,
|
||||
Source: ec.Source,
|
||||
ContentType: ec.ContentType,
|
||||
Extensions: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
for k, v := range ec.Extensions {
|
||||
// eventTypeVersion was retired in v0.2
|
||||
if strings.EqualFold(k, EventTypeVersionKey) {
|
||||
etv, ok := v.(string)
|
||||
if ok && etv != "" {
|
||||
ret.EventTypeVersion = &etv
|
||||
}
|
||||
continue
|
||||
}
|
||||
ret.Extensions[k] = v
|
||||
}
|
||||
if len(ret.Extensions) == 0 {
|
||||
ret.Extensions = nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// AsV02 implements EventContextConverter.AsV02
|
||||
func (ec EventContextV02) AsV02() *EventContextV02 {
|
||||
ec.SpecVersion = CloudEventsVersionV02
|
||||
return &ec
|
||||
}
|
||||
|
||||
// AsV03 implements EventContextConverter.AsV03
|
||||
func (ec EventContextV02) AsV03() *EventContextV03 {
|
||||
ret := EventContextV03{
|
||||
SpecVersion: CloudEventsVersionV03,
|
||||
ID: ec.ID,
|
||||
Time: ec.Time,
|
||||
Type: ec.Type,
|
||||
SchemaURL: ec.SchemaURL,
|
||||
DataContentType: ec.ContentType,
|
||||
Source: ec.Source,
|
||||
Extensions: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
for k, v := range ec.Extensions {
|
||||
// Subject was introduced in 0.3
|
||||
if strings.EqualFold(k, SubjectKey) {
|
||||
sub, ok := v.(string)
|
||||
if ok && sub != "" {
|
||||
ret.Subject = &sub
|
||||
}
|
||||
continue
|
||||
}
|
||||
// DeprecatedDataContentEncoding was introduced in 0.3
|
||||
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 EventContextV02) AsV1() *EventContextV1 {
|
||||
return ec.AsV03().AsV1()
|
||||
}
|
||||
|
||||
// Validate returns errors based on requirements from the CloudEvents spec.
|
||||
// For more details, see https://github.com/cloudevents/spec/blob/v0.2/spec.md
|
||||
func (ec EventContextV02) 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")
|
||||
}
|
||||
|
||||
// specversion
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
specVersion := strings.TrimSpace(ec.SpecVersion)
|
||||
if specVersion == "" {
|
||||
errors = append(errors, "specversion: 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")
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
// contenttype
|
||||
// Type: String per RFC 2046
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 2046
|
||||
if ec.ContentType != nil {
|
||||
contentType := strings.TrimSpace(*ec.ContentType)
|
||||
if contentType == "" {
|
||||
// TODO: need to test for RFC 2046
|
||||
errors = append(errors, "contenttype: if present, MUST adhere to the format specified in RFC 2046")
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf(strings.Join(errors, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a pretty-printed representation of the EventContext.
|
||||
func (ec EventContextV02) String() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
b.WriteString("Context Attributes,\n")
|
||||
|
||||
b.WriteString(" specversion: " + ec.SpecVersion + "\n")
|
||||
b.WriteString(" type: " + ec.Type + "\n")
|
||||
b.WriteString(" source: " + ec.Source.String() + "\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.ContentType != nil {
|
||||
b.WriteString(" contenttype: " + *ec.ContentType + "\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()
|
||||
}
|
||||
101
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_reader.go
generated
vendored
101
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_reader.go
generated
vendored
|
|
@ -1,101 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Adhere to EventContextReader
|
||||
var _ EventContextReader = (*EventContextV02)(nil)
|
||||
|
||||
// GetSpecVersion implements EventContextReader.GetSpecVersion
|
||||
func (ec EventContextV02) GetSpecVersion() string {
|
||||
if ec.SpecVersion != "" {
|
||||
return ec.SpecVersion
|
||||
}
|
||||
return CloudEventsVersionV02
|
||||
}
|
||||
|
||||
// GetType implements EventContextReader.GetType
|
||||
func (ec EventContextV02) GetType() string {
|
||||
return ec.Type
|
||||
}
|
||||
|
||||
// GetSource implements EventContextReader.GetSource
|
||||
func (ec EventContextV02) GetSource() string {
|
||||
return ec.Source.String()
|
||||
}
|
||||
|
||||
// GetSubject implements EventContextReader.GetSubject
|
||||
func (ec EventContextV02) GetSubject() string {
|
||||
var sub string
|
||||
if err := ec.ExtensionAs(SubjectKey, &sub); err != nil {
|
||||
return ""
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
// GetID implements EventContextReader.GetID
|
||||
func (ec EventContextV02) GetID() string {
|
||||
return ec.ID
|
||||
}
|
||||
|
||||
// GetTime implements EventContextReader.GetTime
|
||||
func (ec EventContextV02) GetTime() time.Time {
|
||||
if ec.Time != nil {
|
||||
return ec.Time.Time
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// GetDataSchema implements EventContextReader.GetDataSchema
|
||||
func (ec EventContextV02) GetDataSchema() string {
|
||||
if ec.SchemaURL != nil {
|
||||
return ec.SchemaURL.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetDataContentType implements EventContextReader.GetDataContentType
|
||||
func (ec EventContextV02) GetDataContentType() string {
|
||||
if ec.ContentType != nil {
|
||||
return *ec.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetDataMediaType implements EventContextReader.GetDataMediaType
|
||||
func (ec EventContextV02) GetDataMediaType() (string, error) {
|
||||
if ec.ContentType != nil {
|
||||
mediaType, _, err := mime.ParseMediaType(*ec.ContentType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return mediaType, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// DeprecatedGetDataContentEncoding implements EventContextReader.DeprecatedGetDataContentEncoding
|
||||
func (ec EventContextV02) DeprecatedGetDataContentEncoding() string {
|
||||
var enc string
|
||||
if err := ec.ExtensionAs(DataContentEncodingKey, &enc); err != nil {
|
||||
return ""
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
// GetExtensions implements EventContextReader.GetExtensions
|
||||
func (ec EventContextV02) GetExtensions() map[string]interface{} {
|
||||
return ec.Extensions
|
||||
}
|
||||
|
||||
// GetExtension implements EventContextReader.GetExtension
|
||||
func (ec EventContextV02) GetExtension(key string) (interface{}, error) {
|
||||
v, ok := caseInsensitiveSearch(key, ec.Extensions)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%q not found", key)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
104
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_writer.go
generated
vendored
104
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_writer.go
generated
vendored
|
|
@ -1,104 +0,0 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// Adhere to EventContextWriter
|
||||
var _ EventContextWriter = (*EventContextV02)(nil)
|
||||
|
||||
// SetSpecVersion implements EventContextWriter.SetSpecVersion
|
||||
func (ec *EventContextV02) SetSpecVersion(v string) error {
|
||||
if v != CloudEventsVersionV02 {
|
||||
return fmt.Errorf("invalid version %q, expecting %q", v, CloudEventsVersionV02)
|
||||
}
|
||||
ec.SpecVersion = CloudEventsVersionV02
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataContentType implements EventContextWriter.SetDataContentType
|
||||
func (ec *EventContextV02) SetDataContentType(ct string) error {
|
||||
ct = strings.TrimSpace(ct)
|
||||
if ct == "" {
|
||||
ec.ContentType = nil
|
||||
} else {
|
||||
ec.ContentType = &ct
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetType implements EventContextWriter.SetType
|
||||
func (ec *EventContextV02) SetType(t string) error {
|
||||
t = strings.TrimSpace(t)
|
||||
ec.Type = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSource implements EventContextWriter.SetSource
|
||||
func (ec *EventContextV02) SetSource(u string) error {
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.Source = types.URLRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSubject implements EventContextWriter.SetSubject
|
||||
func (ec *EventContextV02) SetSubject(s string) error {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return ec.SetExtension(SubjectKey, nil)
|
||||
}
|
||||
return ec.SetExtension(SubjectKey, s)
|
||||
}
|
||||
|
||||
// SetID implements EventContextWriter.SetID
|
||||
func (ec *EventContextV02) 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 *EventContextV02) 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 *EventContextV02) 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.URLRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeprecatedSetDataContentEncoding implements EventContextWriter.DeprecatedSetDataContentEncoding
|
||||
func (ec *EventContextV02) DeprecatedSetDataContentEncoding(e string) error {
|
||||
e = strings.ToLower(strings.TrimSpace(e))
|
||||
if e == "" {
|
||||
return ec.SetExtension(DataContentEncodingKey, nil)
|
||||
}
|
||||
return ec.SetExtension(DataContentEncodingKey, e)
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
)
|
||||
|
||||
// Codec is the interface for transport codecs to convert between transport
|
||||
// specific payloads and the Message interface.
|
||||
type Codec interface {
|
||||
Encode(context.Context, cloudevents.Event) (Message, error)
|
||||
Decode(context.Context, Message) (*cloudevents.Event, error)
|
||||
}
|
||||
|
||||
// ErrMessageEncodingUnknown is an error produced when the encoding for an incoming
|
||||
// message can not be understood.
|
||||
type ErrMessageEncodingUnknown struct {
|
||||
codec string
|
||||
transport string
|
||||
}
|
||||
|
||||
// NewErrMessageEncodingUnknown makes a new ErrMessageEncodingUnknown.
|
||||
func NewErrMessageEncodingUnknown(codec, transport string) *ErrMessageEncodingUnknown {
|
||||
return &ErrMessageEncodingUnknown{
|
||||
codec: codec,
|
||||
transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements error.Error
|
||||
func (e *ErrMessageEncodingUnknown) Error() string {
|
||||
return fmt.Sprintf("message encoding unknown for %s codec on %s transport", e.codec, e.transport)
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
)
|
||||
|
||||
// Codec is the wrapper for all versions of codecs supported by the http
|
||||
// transport.
|
||||
type Codec struct {
|
||||
// Encoding is the setting to inform the DefaultEncodingSelectionFn for
|
||||
// selecting a codec.
|
||||
Encoding Encoding
|
||||
|
||||
// DefaultEncodingSelectionFn allows for encoding selection strategies to be injected.
|
||||
DefaultEncodingSelectionFn EncodingSelector
|
||||
|
||||
v01 *CodecV01
|
||||
v02 *CodecV02
|
||||
v03 *CodecV03
|
||||
v1 *CodecV1
|
||||
|
||||
_v01 sync.Once
|
||||
_v02 sync.Once
|
||||
_v03 sync.Once
|
||||
_v1 sync.Once
|
||||
}
|
||||
|
||||
// Adheres to Codec
|
||||
var _ transport.Codec = (*Codec)(nil)
|
||||
|
||||
func (c *Codec) loadCodec(encoding Encoding) (transport.Codec, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV01, StructuredV01:
|
||||
c._v01.Do(func() {
|
||||
c.v01 = &CodecV01{DefaultEncoding: c.Encoding}
|
||||
})
|
||||
return c.v01, nil
|
||||
case BinaryV02, StructuredV02:
|
||||
c._v02.Do(func() {
|
||||
c.v02 = &CodecV02{DefaultEncoding: c.Encoding}
|
||||
})
|
||||
return c.v02, nil
|
||||
case BinaryV03, StructuredV03, BatchedV03:
|
||||
c._v03.Do(func() {
|
||||
c.v03 = &CodecV03{DefaultEncoding: c.Encoding}
|
||||
})
|
||||
return c.v03, nil
|
||||
case BinaryV1, StructuredV1, BatchedV1:
|
||||
c._v1.Do(func() {
|
||||
c.v1 = &CodecV1{DefaultEncoding: c.Encoding}
|
||||
})
|
||||
return c.v1, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown encoding: %s", encoding)
|
||||
}
|
||||
|
||||
// Encode encodes the provided event into a transport message.
|
||||
func (c *Codec) Encode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
encoding := c.Encoding
|
||||
if encoding == Default && c.DefaultEncodingSelectionFn != nil {
|
||||
encoding = c.DefaultEncodingSelectionFn(ctx, e)
|
||||
}
|
||||
codec, err := c.loadCodec(encoding)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx = cecontext.WithEncoding(ctx, encoding.Name())
|
||||
return codec.Encode(ctx, e)
|
||||
}
|
||||
|
||||
// Decode converts a provided transport message into an Event, or error.
|
||||
func (c *Codec) Decode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
codec, err := c.loadCodec(c.inspectEncoding(ctx, msg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event, err := codec.Decode(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.convertEvent(event)
|
||||
}
|
||||
|
||||
// Give the context back as the user expects
|
||||
func (c *Codec) convertEvent(event *cloudevents.Event) (*cloudevents.Event, error) {
|
||||
if event == nil {
|
||||
return nil, errors.New("event is nil, can not convert")
|
||||
}
|
||||
|
||||
switch c.Encoding {
|
||||
case Default:
|
||||
return event, nil
|
||||
case BinaryV01, StructuredV01:
|
||||
ca := event.Context.AsV01()
|
||||
event.Context = ca
|
||||
return event, nil
|
||||
case BinaryV02, StructuredV02:
|
||||
ca := event.Context.AsV02()
|
||||
event.Context = ca
|
||||
return event, nil
|
||||
case BinaryV03, StructuredV03, BatchedV03:
|
||||
ca := event.Context.AsV03()
|
||||
event.Context = ca
|
||||
return event, nil
|
||||
case BinaryV1, StructuredV1, BatchedV1:
|
||||
ca := event.Context.AsV03()
|
||||
event.Context = ca
|
||||
return event, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %s", c.Encoding)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Codec) inspectEncoding(ctx context.Context, msg transport.Message) Encoding {
|
||||
// Try v1.0.
|
||||
_, _ = c.loadCodec(BinaryV1)
|
||||
encoding := c.v1.inspectEncoding(ctx, msg)
|
||||
if encoding != Unknown {
|
||||
return encoding
|
||||
}
|
||||
|
||||
// Try v0.3.
|
||||
_, _ = c.loadCodec(BinaryV03)
|
||||
encoding = c.v03.inspectEncoding(ctx, msg)
|
||||
if encoding != Unknown {
|
||||
return encoding
|
||||
}
|
||||
|
||||
// Try v0.2.
|
||||
_, _ = c.loadCodec(BinaryV02)
|
||||
encoding = c.v02.inspectEncoding(ctx, msg)
|
||||
if encoding != Unknown {
|
||||
return encoding
|
||||
}
|
||||
|
||||
// Try v0.1 first.
|
||||
_, _ = c.loadCodec(BinaryV01)
|
||||
encoding = c.v01.inspectEncoding(ctx, msg)
|
||||
if encoding != Unknown {
|
||||
return encoding
|
||||
}
|
||||
|
||||
// We do not understand the message encoding.
|
||||
return Unknown
|
||||
}
|
||||
44
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_structured.go
generated
vendored
44
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_structured.go
generated
vendored
|
|
@ -1,44 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
)
|
||||
|
||||
// CodecStructured represents an structured http transport codec for all versions.
|
||||
// Intended to be used as a base class.
|
||||
type CodecStructured struct {
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
func (v CodecStructured) encodeStructured(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", cloudevents.ApplicationCloudEventsJSON)
|
||||
|
||||
body, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
Header: header,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (v CodecStructured) decodeStructured(ctx context.Context, version string, msg transport.Message) (*cloudevents.Event, error) {
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert transport.Message to http.Message")
|
||||
}
|
||||
event := cloudevents.New(version)
|
||||
err := json.Unmarshal(m.Body, &event)
|
||||
return &event, err
|
||||
}
|
||||
232
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v01.go
generated
vendored
232
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v01.go
generated
vendored
|
|
@ -1,232 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// CodecV01 represents a http transport codec that uses CloudEvents spec v0.1
|
||||
type CodecV01 struct {
|
||||
CodecStructured
|
||||
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
// Adheres to Codec
|
||||
var _ transport.Codec = (*CodecV01)(nil)
|
||||
|
||||
// Encode implements Codec.Encode
|
||||
func (v CodecV01) Encode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
encoding := v.DefaultEncoding
|
||||
strEnc := cecontext.EncodingFrom(ctx)
|
||||
if strEnc != "" {
|
||||
switch strEnc {
|
||||
case Binary:
|
||||
encoding = BinaryV01
|
||||
case Structured:
|
||||
encoding = StructuredV01
|
||||
}
|
||||
}
|
||||
|
||||
_, r := observability.NewReporter(context.Background(), CodecObserved{o: reportEncode, c: encoding.Codec()})
|
||||
m, err := v.obsEncode(ctx, e, encoding)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (v CodecV01) obsEncode(ctx context.Context, e cloudevents.Event, encoding Encoding) (transport.Message, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV01:
|
||||
return v.encodeBinary(ctx, e)
|
||||
case StructuredV01:
|
||||
return v.encodeStructured(ctx, e)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %d", encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// Decode implements Codec.Decode
|
||||
func (v CodecV01) Decode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportDecode, c: v.inspectEncoding(ctx, msg).Codec()}) // TODO: inspectEncoding is not free.
|
||||
e, err := v.obsDecode(ctx, msg)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return e, err
|
||||
}
|
||||
|
||||
func (v CodecV01) obsDecode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
switch v.inspectEncoding(ctx, msg) {
|
||||
case BinaryV01:
|
||||
return v.decodeBinary(ctx, msg)
|
||||
case StructuredV01:
|
||||
return v.decodeStructured(ctx, cloudevents.CloudEventsVersionV01, msg)
|
||||
default:
|
||||
return nil, transport.NewErrMessageEncodingUnknown("v01", TransportName)
|
||||
}
|
||||
}
|
||||
|
||||
func (v CodecV01) encodeBinary(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
header, err := v.toHeaders(e.Context.AsV01())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := e.DataBytes()
|
||||
if err != nil {
|
||||
panic("encode")
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
Header: header,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (v CodecV01) toHeaders(ec *cloudevents.EventContextV01) (http.Header, error) {
|
||||
// Preserve case in v0.1, even though HTTP headers are case-insensitive.
|
||||
h := http.Header{}
|
||||
h["CE-CloudEventsVersion"] = []string{ec.CloudEventsVersion}
|
||||
h["CE-EventID"] = []string{ec.EventID}
|
||||
h["CE-EventType"] = []string{ec.EventType}
|
||||
h["CE-Source"] = []string{ec.Source.String()}
|
||||
if ec.EventTime != nil && !ec.EventTime.IsZero() {
|
||||
h["CE-EventTime"] = []string{ec.EventTime.String()}
|
||||
}
|
||||
if ec.EventTypeVersion != nil {
|
||||
h["CE-EventTypeVersion"] = []string{*ec.EventTypeVersion}
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
h["CE-DataSchema"] = []string{ec.SchemaURL.String()}
|
||||
}
|
||||
if ec.ContentType != nil && *ec.ContentType != "" {
|
||||
h.Set("Content-Type", *ec.ContentType)
|
||||
}
|
||||
|
||||
// Regarding Extensions, v0.1 Spec says the following:
|
||||
// * Each map entry name MUST be prefixed with "CE-X-"
|
||||
// * Each map entry name's first character MUST be capitalized
|
||||
for k, v := range ec.Extensions {
|
||||
encoded, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h["CE-X-"+strings.Title(k)] = []string{string(encoded)}
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (v CodecV01) decodeBinary(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert transport.Message to http.Message")
|
||||
}
|
||||
ca, err := v.fromHeaders(m.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var body interface{}
|
||||
if len(m.Body) > 0 {
|
||||
body = m.Body
|
||||
}
|
||||
return &cloudevents.Event{
|
||||
Context: &ca,
|
||||
Data: body,
|
||||
DataEncoded: body != nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v CodecV01) fromHeaders(h http.Header) (cloudevents.EventContextV01, error) {
|
||||
// Normalize headers.
|
||||
for k, v := range h {
|
||||
ck := textproto.CanonicalMIMEHeaderKey(k)
|
||||
if k != ck {
|
||||
h[ck] = v
|
||||
}
|
||||
}
|
||||
|
||||
ec := cloudevents.EventContextV01{}
|
||||
ec.CloudEventsVersion = h.Get("CE-CloudEventsVersion")
|
||||
h.Del("CE-CloudEventsVersion")
|
||||
ec.EventID = h.Get("CE-EventID")
|
||||
h.Del("CE-EventID")
|
||||
ec.EventType = h.Get("CE-EventType")
|
||||
h.Del("CE-EventType")
|
||||
source := types.ParseURLRef(h.Get("CE-Source"))
|
||||
h.Del("CE-Source")
|
||||
if source != nil {
|
||||
ec.Source = *source
|
||||
}
|
||||
var err error
|
||||
ec.EventTime, err = types.ParseTimestamp(h.Get("CE-EventTime"))
|
||||
if err != nil {
|
||||
return ec, err
|
||||
}
|
||||
h.Del("CE-EventTime")
|
||||
etv := h.Get("CE-EventTypeVersion")
|
||||
h.Del("CE-EventTypeVersion")
|
||||
if etv != "" {
|
||||
ec.EventTypeVersion = &etv
|
||||
}
|
||||
ec.SchemaURL = types.ParseURLRef(h.Get("CE-DataSchema"))
|
||||
h.Del("CE-DataSchema")
|
||||
et := h.Get("Content-Type")
|
||||
if et != "" {
|
||||
ec.ContentType = &et
|
||||
}
|
||||
|
||||
extensions := make(map[string]interface{})
|
||||
for k, v := range h {
|
||||
if len(k) > len("CE-X-") && strings.EqualFold(k[:len("CE-X-")], "CE-X-") {
|
||||
key := k[len("CE-X-"):]
|
||||
var tmp interface{}
|
||||
if err := json.Unmarshal([]byte(v[0]), &tmp); err == nil {
|
||||
extensions[key] = tmp
|
||||
} else {
|
||||
// If we can't unmarshal the data, treat it as a string.
|
||||
extensions[key] = v[0]
|
||||
}
|
||||
h.Del(k)
|
||||
}
|
||||
}
|
||||
if len(extensions) > 0 {
|
||||
ec.Extensions = extensions
|
||||
}
|
||||
return ec, nil
|
||||
}
|
||||
|
||||
func (v CodecV01) inspectEncoding(ctx context.Context, msg transport.Message) Encoding {
|
||||
version := msg.CloudEventsVersion()
|
||||
if version != cloudevents.CloudEventsVersionV01 {
|
||||
return Unknown
|
||||
}
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return Unknown
|
||||
}
|
||||
contentType := m.Header.Get("Content-Type")
|
||||
if contentType == cloudevents.ApplicationCloudEventsJSON {
|
||||
return StructuredV01
|
||||
}
|
||||
return BinaryV01
|
||||
}
|
||||
261
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v02.go
generated
vendored
261
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v02.go
generated
vendored
|
|
@ -1,261 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// CodecV02 represents a http transport codec that uses CloudEvents spec v0.2
|
||||
type CodecV02 struct {
|
||||
CodecStructured
|
||||
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
// Adheres to Codec
|
||||
var _ transport.Codec = (*CodecV02)(nil)
|
||||
|
||||
// Encode implements Codec.Encode
|
||||
func (v CodecV02) Encode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
encoding := v.DefaultEncoding
|
||||
strEnc := cecontext.EncodingFrom(ctx)
|
||||
if strEnc != "" {
|
||||
switch strEnc {
|
||||
case Binary:
|
||||
encoding = BinaryV02
|
||||
case Structured:
|
||||
encoding = StructuredV02
|
||||
}
|
||||
}
|
||||
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportEncode, c: encoding.Codec()})
|
||||
m, err := v.obsEncode(ctx, e, encoding)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (v CodecV02) obsEncode(ctx context.Context, e cloudevents.Event, encoding Encoding) (transport.Message, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV02:
|
||||
return v.encodeBinary(ctx, e)
|
||||
case StructuredV02:
|
||||
return v.encodeStructured(ctx, e)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %d", encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// Decode implements Codec.Decode
|
||||
func (v CodecV02) Decode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportDecode, c: v.inspectEncoding(ctx, msg).Codec()}) // TODO: inspectEncoding is not free.
|
||||
e, err := v.obsDecode(ctx, msg)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return e, err
|
||||
}
|
||||
|
||||
func (v CodecV02) obsDecode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
switch v.inspectEncoding(ctx, msg) {
|
||||
case BinaryV02:
|
||||
return v.decodeBinary(ctx, msg)
|
||||
case StructuredV02:
|
||||
return v.decodeStructured(ctx, cloudevents.CloudEventsVersionV02, msg)
|
||||
default:
|
||||
return nil, transport.NewErrMessageEncodingUnknown("v02", TransportName)
|
||||
}
|
||||
}
|
||||
|
||||
func (v CodecV02) encodeBinary(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
header, err := v.toHeaders(e.Context.AsV02())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := e.DataBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
Header: header,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (v CodecV02) toHeaders(ec *cloudevents.EventContextV02) (http.Header, error) {
|
||||
h := http.Header{}
|
||||
h.Set("ce-specversion", ec.SpecVersion)
|
||||
h.Set("ce-type", ec.Type)
|
||||
h.Set("ce-source", ec.Source.String())
|
||||
h.Set("ce-id", ec.ID)
|
||||
if ec.Time != nil && !ec.Time.IsZero() {
|
||||
h.Set("ce-time", ec.Time.String())
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
h.Set("ce-schemaurl", ec.SchemaURL.String())
|
||||
}
|
||||
if ec.ContentType != nil && *ec.ContentType != "" {
|
||||
h.Set("Content-Type", *ec.ContentType)
|
||||
}
|
||||
for k, v := range ec.Extensions {
|
||||
// Per spec, map-valued extensions are converted to a list of headers as:
|
||||
// CE-attrib-key
|
||||
if mapVal, ok := v.(map[string]interface{}); ok {
|
||||
for subkey, subval := range mapVal {
|
||||
encoded, err := json.Marshal(subval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.Set("ce-"+k+"-"+subkey, string(encoded))
|
||||
}
|
||||
continue
|
||||
}
|
||||
encoded, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.Set("ce-"+k, string(encoded))
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (v CodecV02) decodeBinary(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert transport.Message to http.Message")
|
||||
}
|
||||
ca, err := v.fromHeaders(m.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var body interface{}
|
||||
if len(m.Body) > 0 {
|
||||
body = m.Body
|
||||
}
|
||||
return &cloudevents.Event{
|
||||
Context: &ca,
|
||||
Data: body,
|
||||
DataEncoded: body != nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v CodecV02) fromHeaders(h http.Header) (cloudevents.EventContextV02, error) {
|
||||
// Normalize headers.
|
||||
for k, v := range h {
|
||||
ck := textproto.CanonicalMIMEHeaderKey(k)
|
||||
if k != ck {
|
||||
delete(h, k)
|
||||
h[ck] = v
|
||||
}
|
||||
}
|
||||
|
||||
ec := cloudevents.EventContextV02{}
|
||||
|
||||
ec.SpecVersion = h.Get("ce-specversion")
|
||||
h.Del("ce-specversion")
|
||||
|
||||
ec.ID = h.Get("ce-id")
|
||||
h.Del("ce-id")
|
||||
|
||||
ec.Type = h.Get("ce-type")
|
||||
h.Del("ce-type")
|
||||
|
||||
source := types.ParseURLRef(h.Get("ce-source"))
|
||||
if source != nil {
|
||||
ec.Source = *source
|
||||
}
|
||||
h.Del("ce-source")
|
||||
|
||||
var err error
|
||||
ec.Time, err = types.ParseTimestamp(h.Get("ce-time"))
|
||||
if err != nil {
|
||||
return ec, err
|
||||
}
|
||||
h.Del("ce-time")
|
||||
|
||||
ec.SchemaURL = types.ParseURLRef(h.Get("ce-schemaurl"))
|
||||
h.Del("ce-schemaurl")
|
||||
|
||||
contentType := h.Get("Content-Type")
|
||||
if contentType != "" {
|
||||
ec.ContentType = &contentType
|
||||
}
|
||||
h.Del("Content-Type")
|
||||
|
||||
// At this point, we have deleted all the known headers.
|
||||
// Everything left is assumed to be an extension.
|
||||
|
||||
extensions := make(map[string]interface{})
|
||||
for k, v := range h {
|
||||
if len(k) > len("ce-") && strings.EqualFold(k[:len("ce-")], "ce-") {
|
||||
ak := strings.ToLower(k[len("ce-"):])
|
||||
if i := strings.Index(ak, "-"); i > 0 {
|
||||
// attrib-key
|
||||
attrib := ak[:i]
|
||||
key := ak[(i + 1):]
|
||||
if xv, ok := extensions[attrib]; ok {
|
||||
if m, ok := xv.(map[string]interface{}); ok {
|
||||
m[key] = v
|
||||
continue
|
||||
}
|
||||
// TODO: revisit how we want to bubble errors up.
|
||||
return ec, fmt.Errorf("failed to process map type extension")
|
||||
} else {
|
||||
m := make(map[string]interface{})
|
||||
m[key] = v
|
||||
extensions[attrib] = m
|
||||
}
|
||||
} else {
|
||||
// key
|
||||
var tmp interface{}
|
||||
if err := json.Unmarshal([]byte(v[0]), &tmp); err == nil {
|
||||
extensions[ak] = tmp
|
||||
} else {
|
||||
// If we can't unmarshal the data, treat it as a string.
|
||||
extensions[ak] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(extensions) > 0 {
|
||||
ec.Extensions = extensions
|
||||
}
|
||||
return ec, nil
|
||||
}
|
||||
|
||||
func (v CodecV02) inspectEncoding(ctx context.Context, msg transport.Message) Encoding {
|
||||
version := msg.CloudEventsVersion()
|
||||
if version != cloudevents.CloudEventsVersionV02 {
|
||||
return Unknown
|
||||
}
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return Unknown
|
||||
}
|
||||
contentType := m.Header.Get("Content-Type")
|
||||
if contentType == cloudevents.ApplicationCloudEventsJSON {
|
||||
return StructuredV02
|
||||
}
|
||||
return BinaryV02
|
||||
}
|
||||
302
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v03.go
generated
vendored
302
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v03.go
generated
vendored
|
|
@ -1,302 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// CodecV03 represents a http transport codec that uses CloudEvents spec v0.3
|
||||
type CodecV03 struct {
|
||||
CodecStructured
|
||||
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
// Adheres to Codec
|
||||
var _ transport.Codec = (*CodecV03)(nil)
|
||||
|
||||
// Encode implements Codec.Encode
|
||||
func (v CodecV03) Encode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
encoding := v.DefaultEncoding
|
||||
strEnc := cecontext.EncodingFrom(ctx)
|
||||
if strEnc != "" {
|
||||
switch strEnc {
|
||||
case Binary:
|
||||
encoding = BinaryV03
|
||||
case Structured:
|
||||
encoding = StructuredV03
|
||||
}
|
||||
}
|
||||
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportEncode, c: encoding.Codec()})
|
||||
m, err := v.obsEncode(ctx, e, encoding)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (v CodecV03) obsEncode(ctx context.Context, e cloudevents.Event, encoding Encoding) (transport.Message, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV03:
|
||||
return v.encodeBinary(ctx, e)
|
||||
case StructuredV03:
|
||||
return v.encodeStructured(ctx, e)
|
||||
case BatchedV03:
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %d", encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// Decode implements Codec.Decode
|
||||
func (v CodecV03) Decode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportDecode, c: v.inspectEncoding(ctx, msg).Codec()}) // TODO: inspectEncoding is not free.
|
||||
e, err := v.obsDecode(ctx, msg)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return e, err
|
||||
}
|
||||
|
||||
func (v CodecV03) obsDecode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
switch v.inspectEncoding(ctx, msg) {
|
||||
case BinaryV03:
|
||||
return v.decodeBinary(ctx, msg)
|
||||
case StructuredV03:
|
||||
return v.decodeStructured(ctx, cloudevents.CloudEventsVersionV03, msg)
|
||||
case BatchedV03:
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
default:
|
||||
return nil, transport.NewErrMessageEncodingUnknown("v03", TransportName)
|
||||
}
|
||||
}
|
||||
|
||||
func (v CodecV03) encodeBinary(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
header, err := v.toHeaders(e.Context.AsV03())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := e.DataBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
Header: header,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (v CodecV03) toHeaders(ec *cloudevents.EventContextV03) (http.Header, error) {
|
||||
h := http.Header{}
|
||||
h.Set("ce-specversion", ec.SpecVersion)
|
||||
h.Set("ce-type", ec.Type)
|
||||
h.Set("ce-source", ec.Source.String())
|
||||
if ec.Subject != nil {
|
||||
h.Set("ce-subject", *ec.Subject)
|
||||
}
|
||||
h.Set("ce-id", ec.ID)
|
||||
if ec.Time != nil && !ec.Time.IsZero() {
|
||||
h.Set("ce-time", ec.Time.String())
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
h.Set("ce-schemaurl", ec.SchemaURL.String())
|
||||
}
|
||||
if ec.DataContentType != nil && *ec.DataContentType != "" {
|
||||
h.Set("Content-Type", *ec.DataContentType)
|
||||
}
|
||||
if ec.DataContentEncoding != nil {
|
||||
h.Set("ce-datacontentencoding", *ec.DataContentEncoding)
|
||||
}
|
||||
|
||||
for k, v := range ec.Extensions {
|
||||
k = strings.ToLower(k)
|
||||
// Per spec, map-valued extensions are converted to a list of headers as:
|
||||
// CE-attrib-key
|
||||
switch v.(type) {
|
||||
case string:
|
||||
h.Set("ce-"+k, v.(string))
|
||||
|
||||
case map[string]interface{}:
|
||||
mapVal := v.(map[string]interface{})
|
||||
|
||||
for subkey, subval := range mapVal {
|
||||
if subvalstr, ok := v.(string); ok {
|
||||
h.Set("ce-"+k+"-"+subkey, subvalstr)
|
||||
continue
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(subval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.Set("ce-"+k+"-"+subkey, string(encoded))
|
||||
}
|
||||
|
||||
default:
|
||||
encoded, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h.Set("ce-"+k, string(encoded))
|
||||
}
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (v CodecV03) decodeBinary(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert transport.Message to http.Message")
|
||||
}
|
||||
ca, err := v.fromHeaders(m.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var body interface{}
|
||||
if len(m.Body) > 0 {
|
||||
body = m.Body
|
||||
}
|
||||
return &cloudevents.Event{
|
||||
Context: &ca,
|
||||
Data: body,
|
||||
DataEncoded: body != nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v CodecV03) fromHeaders(h http.Header) (cloudevents.EventContextV03, error) {
|
||||
// Normalize headers.
|
||||
for k, v := range h {
|
||||
ck := textproto.CanonicalMIMEHeaderKey(k)
|
||||
if k != ck {
|
||||
delete(h, k)
|
||||
h[ck] = v
|
||||
}
|
||||
}
|
||||
|
||||
ec := cloudevents.EventContextV03{}
|
||||
|
||||
ec.SpecVersion = h.Get("ce-specversion")
|
||||
h.Del("ce-specversion")
|
||||
|
||||
ec.ID = h.Get("ce-id")
|
||||
h.Del("ce-id")
|
||||
|
||||
ec.Type = h.Get("ce-type")
|
||||
h.Del("ce-type")
|
||||
|
||||
source := types.ParseURLRef(h.Get("ce-source"))
|
||||
if source != nil {
|
||||
ec.Source = *source
|
||||
}
|
||||
h.Del("ce-source")
|
||||
|
||||
subject := h.Get("ce-subject")
|
||||
if subject != "" {
|
||||
ec.Subject = &subject
|
||||
}
|
||||
h.Del("ce-subject")
|
||||
|
||||
var err error
|
||||
ec.Time, err = types.ParseTimestamp(h.Get("ce-time"))
|
||||
if err != nil {
|
||||
return ec, err
|
||||
}
|
||||
h.Del("ce-time")
|
||||
|
||||
ec.SchemaURL = types.ParseURLRef(h.Get("ce-schemaurl"))
|
||||
h.Del("ce-schemaurl")
|
||||
|
||||
contentType := h.Get("Content-Type")
|
||||
if contentType != "" {
|
||||
ec.DataContentType = &contentType
|
||||
}
|
||||
h.Del("Content-Type")
|
||||
|
||||
dataContentEncoding := h.Get("ce-datacontentencoding")
|
||||
if dataContentEncoding != "" {
|
||||
ec.DataContentEncoding = &dataContentEncoding
|
||||
}
|
||||
h.Del("ce-datacontentencoding")
|
||||
|
||||
// At this point, we have deleted all the known headers.
|
||||
// Everything left is assumed to be an extension.
|
||||
|
||||
extensions := make(map[string]interface{})
|
||||
for k, v := range h {
|
||||
k = strings.ToLower(k)
|
||||
if len(k) > len("ce-") && strings.EqualFold(k[:len("ce-")], "ce-") {
|
||||
ak := strings.ToLower(k[len("ce-"):])
|
||||
if i := strings.Index(ak, "-"); i > 0 {
|
||||
// attrib-key
|
||||
attrib := ak[:i]
|
||||
key := ak[(i + 1):]
|
||||
if xv, ok := extensions[attrib]; ok {
|
||||
if m, ok := xv.(map[string]interface{}); ok {
|
||||
m[key] = v
|
||||
continue
|
||||
}
|
||||
// TODO: revisit how we want to bubble errors up.
|
||||
return ec, fmt.Errorf("failed to process map type extension")
|
||||
} else {
|
||||
m := make(map[string]interface{})
|
||||
m[key] = v
|
||||
extensions[attrib] = m
|
||||
}
|
||||
} else {
|
||||
// key
|
||||
var tmp interface{}
|
||||
if err := json.Unmarshal([]byte(v[0]), &tmp); err == nil {
|
||||
extensions[ak] = tmp
|
||||
} else {
|
||||
// If we can't unmarshal the data, treat it as a string.
|
||||
extensions[ak] = v[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(extensions) > 0 {
|
||||
ec.Extensions = extensions
|
||||
}
|
||||
return ec, nil
|
||||
}
|
||||
|
||||
func (v CodecV03) inspectEncoding(ctx context.Context, msg transport.Message) Encoding {
|
||||
version := msg.CloudEventsVersion()
|
||||
if version != cloudevents.CloudEventsVersionV03 {
|
||||
return Unknown
|
||||
}
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return Unknown
|
||||
}
|
||||
contentType := m.Header.Get("Content-Type")
|
||||
if contentType == cloudevents.ApplicationCloudEventsJSON {
|
||||
return StructuredV03
|
||||
}
|
||||
if contentType == cloudevents.ApplicationCloudEventsBatchJSON {
|
||||
return BatchedV03
|
||||
}
|
||||
return BinaryV03
|
||||
}
|
||||
245
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v1.go
generated
vendored
245
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v1.go
generated
vendored
|
|
@ -1,245 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// CodecV1 represents a http transport codec that uses CloudEvents spec v1.0
|
||||
type CodecV1 struct {
|
||||
CodecStructured
|
||||
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
// Adheres to Codec
|
||||
var _ transport.Codec = (*CodecV1)(nil)
|
||||
|
||||
// Encode implements Codec.Encode
|
||||
func (v CodecV1) Encode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
encoding := v.DefaultEncoding
|
||||
strEnc := cecontext.EncodingFrom(ctx)
|
||||
if strEnc != "" {
|
||||
switch strEnc {
|
||||
case Binary:
|
||||
encoding = BinaryV1
|
||||
case Structured:
|
||||
encoding = StructuredV1
|
||||
}
|
||||
}
|
||||
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportEncode, c: encoding.Codec()})
|
||||
m, err := v.obsEncode(ctx, e, encoding)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (v CodecV1) obsEncode(ctx context.Context, e cloudevents.Event, encoding Encoding) (transport.Message, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV1:
|
||||
return v.encodeBinary(ctx, e)
|
||||
case StructuredV1:
|
||||
return v.encodeStructured(ctx, e)
|
||||
case BatchedV1:
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %d", encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// Decode implements Codec.Decode
|
||||
func (v CodecV1) Decode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportDecode, c: v.inspectEncoding(ctx, msg).Codec()}) // TODO: inspectEncoding is not free.
|
||||
e, err := v.obsDecode(ctx, msg)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return e, err
|
||||
}
|
||||
|
||||
func (v CodecV1) obsDecode(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
switch v.inspectEncoding(ctx, msg) {
|
||||
case BinaryV1:
|
||||
return v.decodeBinary(ctx, msg)
|
||||
case StructuredV1:
|
||||
return v.decodeStructured(ctx, cloudevents.CloudEventsVersionV1, msg)
|
||||
case BatchedV1:
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
default:
|
||||
return nil, transport.NewErrMessageEncodingUnknown("V1", TransportName)
|
||||
}
|
||||
}
|
||||
|
||||
func (v CodecV1) encodeBinary(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
header, err := v.toHeaders(e.Context.AsV1())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := e.DataBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
Header: header,
|
||||
Body: body,
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (v CodecV1) toHeaders(ec *cloudevents.EventContextV1) (http.Header, error) {
|
||||
h := http.Header{}
|
||||
h.Set("ce-specversion", ec.SpecVersion)
|
||||
h.Set("ce-type", ec.Type)
|
||||
h.Set("ce-source", ec.Source.String())
|
||||
if ec.Subject != nil {
|
||||
h.Set("ce-subject", *ec.Subject)
|
||||
}
|
||||
h.Set("ce-id", ec.ID)
|
||||
if ec.Time != nil && !ec.Time.IsZero() {
|
||||
h.Set("ce-time", ec.Time.String())
|
||||
}
|
||||
if ec.DataSchema != nil {
|
||||
h.Set("ce-dataschema", ec.DataSchema.String())
|
||||
}
|
||||
if ec.DataContentType != nil && *ec.DataContentType != "" {
|
||||
h.Set("Content-Type", *ec.DataContentType)
|
||||
}
|
||||
|
||||
for k, v := range ec.Extensions {
|
||||
k = strings.ToLower(k)
|
||||
// Per spec, extensions are strings and converted to a list of headers as:
|
||||
// ce-key: value
|
||||
cstr, err := types.Format(v)
|
||||
if err != nil {
|
||||
return h, err
|
||||
}
|
||||
h.Set("ce-"+k, cstr)
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (v CodecV1) decodeBinary(ctx context.Context, msg transport.Message) (*cloudevents.Event, error) {
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to convert transport.Message to http.Message")
|
||||
}
|
||||
ca, err := v.fromHeaders(m.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var body interface{}
|
||||
if len(m.Body) > 0 {
|
||||
body = m.Body
|
||||
}
|
||||
return &cloudevents.Event{
|
||||
Context: &ca,
|
||||
Data: body,
|
||||
DataEncoded: body != nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v CodecV1) fromHeaders(h http.Header) (cloudevents.EventContextV1, error) {
|
||||
// Normalize headers.
|
||||
for k, v := range h {
|
||||
ck := textproto.CanonicalMIMEHeaderKey(k)
|
||||
if k != ck {
|
||||
delete(h, k)
|
||||
h[ck] = v
|
||||
}
|
||||
}
|
||||
|
||||
ec := cloudevents.EventContextV1{}
|
||||
|
||||
ec.SpecVersion = h.Get("ce-specversion")
|
||||
h.Del("ce-specversion")
|
||||
|
||||
ec.ID = h.Get("ce-id")
|
||||
h.Del("ce-id")
|
||||
|
||||
ec.Type = h.Get("ce-type")
|
||||
h.Del("ce-type")
|
||||
|
||||
source := types.ParseURIRef(h.Get("ce-source"))
|
||||
if source != nil {
|
||||
ec.Source = *source
|
||||
}
|
||||
h.Del("ce-source")
|
||||
|
||||
subject := h.Get("ce-subject")
|
||||
if subject != "" {
|
||||
ec.Subject = &subject
|
||||
}
|
||||
h.Del("ce-subject")
|
||||
|
||||
var err error
|
||||
ec.Time, err = types.ParseTimestamp(h.Get("ce-time"))
|
||||
if err != nil {
|
||||
return ec, err
|
||||
}
|
||||
h.Del("ce-time")
|
||||
|
||||
ec.DataSchema = types.ParseURI(h.Get("ce-dataschema"))
|
||||
h.Del("ce-dataschema")
|
||||
|
||||
contentType := h.Get("Content-Type")
|
||||
if contentType != "" {
|
||||
ec.DataContentType = &contentType
|
||||
}
|
||||
h.Del("Content-Type")
|
||||
|
||||
// At this point, we have deleted all the known headers.
|
||||
// Everything left is assumed to be an extension.
|
||||
|
||||
extensions := make(map[string]interface{})
|
||||
for k := range h {
|
||||
k = strings.ToLower(k)
|
||||
if len(k) > len("ce-") && strings.EqualFold(k[:len("ce-")], "ce-") {
|
||||
ak := strings.ToLower(k[len("ce-"):])
|
||||
extensions[ak] = h.Get(k)
|
||||
}
|
||||
}
|
||||
if len(extensions) > 0 {
|
||||
ec.Extensions = extensions
|
||||
}
|
||||
return ec, nil
|
||||
}
|
||||
|
||||
func (v CodecV1) inspectEncoding(ctx context.Context, msg transport.Message) Encoding {
|
||||
version := msg.CloudEventsVersion()
|
||||
if version != cloudevents.CloudEventsVersionV1 {
|
||||
return Unknown
|
||||
}
|
||||
m, ok := msg.(*Message)
|
||||
if !ok {
|
||||
return Unknown
|
||||
}
|
||||
contentType := m.Header.Get("Content-Type")
|
||||
if contentType == cloudevents.ApplicationCloudEventsJSON {
|
||||
return StructuredV1
|
||||
}
|
||||
if contentType == cloudevents.ApplicationCloudEventsBatchJSON {
|
||||
return BatchedV1
|
||||
}
|
||||
return BinaryV1
|
||||
}
|
||||
207
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/context.go
generated
vendored
207
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/context.go
generated
vendored
|
|
@ -1,207 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TransportContext allows a Receiver to understand the context of a request.
|
||||
type TransportContext struct {
|
||||
URI string
|
||||
Host string
|
||||
Method string
|
||||
Header http.Header
|
||||
StatusCode int
|
||||
|
||||
// IgnoreHeaderPrefixes controls what comes back from AttendToHeaders.
|
||||
// AttendToHeaders controls what is output for .String()
|
||||
IgnoreHeaderPrefixes []string
|
||||
}
|
||||
|
||||
// NewTransportContext creates a new TransportContext from a http.Request.
|
||||
func NewTransportContext(req *http.Request) TransportContext {
|
||||
var tx *TransportContext
|
||||
if req != nil {
|
||||
tx = &TransportContext{
|
||||
URI: req.RequestURI,
|
||||
Host: req.Host,
|
||||
Method: req.Method,
|
||||
Header: req.Header,
|
||||
}
|
||||
} else {
|
||||
tx = &TransportContext{}
|
||||
}
|
||||
tx.AddIgnoreHeaderPrefix("accept-encoding", "user-agent", "connection", "content-type")
|
||||
return *tx
|
||||
}
|
||||
|
||||
// NewTransportContextFromResponse creates a new TransportContext from a http.Response.
|
||||
// If `res` is nil, it returns a context with a http.StatusInternalServerError status code.
|
||||
func NewTransportContextFromResponse(res *http.Response) TransportContext {
|
||||
var tx *TransportContext
|
||||
if res != nil {
|
||||
tx = &TransportContext{
|
||||
Header: res.Header,
|
||||
StatusCode: res.StatusCode,
|
||||
}
|
||||
} else {
|
||||
tx = &TransportContext{StatusCode: http.StatusInternalServerError}
|
||||
}
|
||||
tx.AddIgnoreHeaderPrefix("accept-encoding", "user-agent", "connection", "content-type")
|
||||
return *tx
|
||||
}
|
||||
|
||||
// TransportResponseContext allows a Receiver response with http transport specific fields.
|
||||
type TransportResponseContext struct {
|
||||
// Header will be merged with the response headers.
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
// AttendToHeaders returns the list of headers that exist in the TransportContext that are not currently in
|
||||
// tx.IgnoreHeaderPrefix.
|
||||
func (tx TransportContext) AttendToHeaders() []string {
|
||||
a := []string(nil)
|
||||
if tx.Header != nil && len(tx.Header) > 0 {
|
||||
for k := range tx.Header {
|
||||
if tx.shouldIgnoreHeader(k) {
|
||||
continue
|
||||
}
|
||||
a = append(a, k)
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (tx TransportContext) shouldIgnoreHeader(h string) bool {
|
||||
for _, v := range tx.IgnoreHeaderPrefixes {
|
||||
if strings.HasPrefix(strings.ToLower(h), strings.ToLower(v)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String generates a pretty-printed version of the resource as a string.
|
||||
func (tx TransportContext) String() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
b.WriteString("Transport Context,\n")
|
||||
|
||||
empty := b.Len()
|
||||
|
||||
if tx.URI != "" {
|
||||
b.WriteString(" URI: " + tx.URI + "\n")
|
||||
}
|
||||
if tx.Host != "" {
|
||||
b.WriteString(" Host: " + tx.Host + "\n")
|
||||
}
|
||||
|
||||
if tx.Method != "" {
|
||||
b.WriteString(" Method: " + tx.Method + "\n")
|
||||
}
|
||||
|
||||
if tx.StatusCode != 0 {
|
||||
b.WriteString(" StatusCode: " + strconv.Itoa(tx.StatusCode) + "\n")
|
||||
}
|
||||
|
||||
if tx.Header != nil && len(tx.Header) > 0 {
|
||||
b.WriteString(" Header:\n")
|
||||
for _, k := range tx.AttendToHeaders() {
|
||||
b.WriteString(fmt.Sprintf(" %s: %s\n", k, tx.Header.Get(k)))
|
||||
}
|
||||
}
|
||||
|
||||
if b.Len() == empty {
|
||||
b.WriteString(" nil\n")
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// AddIgnoreHeaderPrefix controls what header key is to be attended to and/or printed.
|
||||
func (tx *TransportContext) AddIgnoreHeaderPrefix(prefix ...string) {
|
||||
if tx.IgnoreHeaderPrefixes == nil {
|
||||
tx.IgnoreHeaderPrefixes = []string(nil)
|
||||
}
|
||||
tx.IgnoreHeaderPrefixes = append(tx.IgnoreHeaderPrefixes, prefix...)
|
||||
}
|
||||
|
||||
// Opaque key type used to store TransportContext
|
||||
type transportContextKeyType struct{}
|
||||
|
||||
var transportContextKey = transportContextKeyType{}
|
||||
|
||||
// WithTransportContext return a context with the given TransportContext into the provided context object.
|
||||
func WithTransportContext(ctx context.Context, tcxt TransportContext) context.Context {
|
||||
return context.WithValue(ctx, transportContextKey, tcxt)
|
||||
}
|
||||
|
||||
// TransportContextFrom pulls a TransportContext out of a context. Always
|
||||
// returns a non-nil object.
|
||||
func TransportContextFrom(ctx context.Context) TransportContext {
|
||||
tctx := ctx.Value(transportContextKey)
|
||||
if tctx != nil {
|
||||
if tx, ok := tctx.(TransportContext); ok {
|
||||
return tx
|
||||
}
|
||||
if tx, ok := tctx.(*TransportContext); ok {
|
||||
return *tx
|
||||
}
|
||||
}
|
||||
return TransportContext{}
|
||||
}
|
||||
|
||||
// Opaque key type used to store Headers
|
||||
type headerKeyType struct{}
|
||||
|
||||
var headerKey = headerKeyType{}
|
||||
|
||||
// ContextWithHeader returns a context with a header added to the given context.
|
||||
// Can be called multiple times to set multiple header key/value pairs.
|
||||
func ContextWithHeader(ctx context.Context, key, value string) context.Context {
|
||||
header := HeaderFrom(ctx)
|
||||
header.Add(key, value)
|
||||
return context.WithValue(ctx, headerKey, header)
|
||||
}
|
||||
|
||||
// HeaderFrom extracts the header object in the given context. Always returns a non-nil Header.
|
||||
func HeaderFrom(ctx context.Context) http.Header {
|
||||
ch := http.Header{}
|
||||
header := ctx.Value(headerKey)
|
||||
if header != nil {
|
||||
if h, ok := header.(http.Header); ok {
|
||||
copyHeaders(h, ch)
|
||||
}
|
||||
}
|
||||
return ch
|
||||
}
|
||||
|
||||
// Opaque key type used to store long poll target.
|
||||
type longPollTargetKeyType struct{}
|
||||
|
||||
var longPollTargetKey = longPollTargetKeyType{}
|
||||
|
||||
// WithLongPollTarget returns a new context with the given long poll target.
|
||||
// `target` should be a full URL and will be injected into the long polling
|
||||
// http request within StartReceiver.
|
||||
func ContextWithLongPollTarget(ctx context.Context, target string) context.Context {
|
||||
return context.WithValue(ctx, longPollTargetKey, target)
|
||||
}
|
||||
|
||||
// LongPollTargetFrom looks in the given context and returns `target` as a
|
||||
// parsed url if found and valid, otherwise nil.
|
||||
func LongPollTargetFrom(ctx context.Context) *url.URL {
|
||||
c := ctx.Value(longPollTargetKey)
|
||||
if c != nil {
|
||||
if s, ok := c.(string); ok && s != "" {
|
||||
if target, err := url.Parse(s); err == nil {
|
||||
return target
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
Package http implements the CloudEvent transport implementation using HTTP.
|
||||
*/
|
||||
package http
|
||||
205
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/encoding.go
generated
vendored
205
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/encoding.go
generated
vendored
|
|
@ -1,205 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context"
|
||||
)
|
||||
|
||||
// Encoding to use for HTTP transport.
|
||||
type Encoding int32
|
||||
|
||||
type EncodingSelector func(context.Context, cloudevents.Event) Encoding
|
||||
|
||||
const (
|
||||
// Default
|
||||
Default Encoding = iota
|
||||
// BinaryV01 is Binary CloudEvents spec v0.1.
|
||||
BinaryV01
|
||||
// StructuredV01 is Structured CloudEvents spec v0.1.
|
||||
StructuredV01
|
||||
// BinaryV02 is Binary CloudEvents spec v0.2.
|
||||
BinaryV02
|
||||
// StructuredV02 is Structured CloudEvents spec v0.2.
|
||||
StructuredV02
|
||||
// BinaryV03 is Binary CloudEvents spec v0.3.
|
||||
BinaryV03
|
||||
// StructuredV03 is Structured CloudEvents spec v0.3.
|
||||
StructuredV03
|
||||
// BatchedV03 is Batched CloudEvents spec v0.3.
|
||||
BatchedV03
|
||||
// BinaryV1 is Binary CloudEvents spec v1.0.
|
||||
BinaryV1
|
||||
// StructuredV03 is Structured CloudEvents spec v1.0.
|
||||
StructuredV1
|
||||
// BatchedV1 is Batched CloudEvents spec v1.0.
|
||||
BatchedV1
|
||||
|
||||
// Unknown is unknown.
|
||||
Unknown
|
||||
|
||||
// Binary is used for Context Based Encoding Selections to use the
|
||||
// DefaultBinaryEncodingSelectionStrategy
|
||||
Binary = "binary"
|
||||
|
||||
// Structured is used for Context Based Encoding Selections to use the
|
||||
// DefaultStructuredEncodingSelectionStrategy
|
||||
Structured = "structured"
|
||||
|
||||
// Batched is used for Context Based Encoding Selections to use the
|
||||
// DefaultStructuredEncodingSelectionStrategy
|
||||
Batched = "batched"
|
||||
)
|
||||
|
||||
func ContextBasedEncodingSelectionStrategy(ctx context.Context, e cloudevents.Event) Encoding {
|
||||
encoding := cecontext.EncodingFrom(ctx)
|
||||
switch encoding {
|
||||
case "", Binary:
|
||||
return DefaultBinaryEncodingSelectionStrategy(ctx, e)
|
||||
case Structured:
|
||||
return DefaultStructuredEncodingSelectionStrategy(ctx, e)
|
||||
}
|
||||
return Default
|
||||
}
|
||||
|
||||
// DefaultBinaryEncodingSelectionStrategy implements a selection process for
|
||||
// which binary encoding to use based on spec version of the event.
|
||||
func DefaultBinaryEncodingSelectionStrategy(ctx context.Context, e cloudevents.Event) Encoding {
|
||||
switch e.SpecVersion() {
|
||||
case cloudevents.CloudEventsVersionV01:
|
||||
return BinaryV01
|
||||
case cloudevents.CloudEventsVersionV02:
|
||||
return BinaryV02
|
||||
case cloudevents.CloudEventsVersionV03:
|
||||
return BinaryV03
|
||||
case cloudevents.CloudEventsVersionV1:
|
||||
return BinaryV1
|
||||
}
|
||||
// Unknown version, return Default.
|
||||
return Default
|
||||
}
|
||||
|
||||
// DefaultStructuredEncodingSelectionStrategy implements a selection process
|
||||
// for which structured encoding to use based on spec version of the event.
|
||||
func DefaultStructuredEncodingSelectionStrategy(ctx context.Context, e cloudevents.Event) Encoding {
|
||||
switch e.SpecVersion() {
|
||||
case cloudevents.CloudEventsVersionV01:
|
||||
return StructuredV01
|
||||
case cloudevents.CloudEventsVersionV02:
|
||||
return StructuredV02
|
||||
case cloudevents.CloudEventsVersionV03:
|
||||
return StructuredV03
|
||||
case cloudevents.CloudEventsVersionV1:
|
||||
return StructuredV1
|
||||
}
|
||||
// Unknown version, return Default.
|
||||
return Default
|
||||
}
|
||||
|
||||
// String pretty-prints the encoding as a string.
|
||||
func (e Encoding) String() string {
|
||||
switch e {
|
||||
case Default:
|
||||
return "Default Encoding " + e.Version()
|
||||
|
||||
// Binary
|
||||
case BinaryV01, BinaryV02, BinaryV03, BinaryV1:
|
||||
return "Binary Encoding " + e.Version()
|
||||
|
||||
// Structured
|
||||
case StructuredV01, StructuredV02, StructuredV03, StructuredV1:
|
||||
return "Structured Encoding " + e.Version()
|
||||
|
||||
// Batched
|
||||
case BatchedV03, BatchedV1:
|
||||
return "Batched Encoding " + e.Version()
|
||||
|
||||
default:
|
||||
return "Unknown Encoding"
|
||||
}
|
||||
}
|
||||
|
||||
// Version pretty-prints the encoding version as a string.
|
||||
func (e Encoding) Version() string {
|
||||
switch e {
|
||||
case Default:
|
||||
return "Default"
|
||||
|
||||
// Version 0.1
|
||||
case BinaryV01, StructuredV01:
|
||||
return "v0.1"
|
||||
|
||||
// Version 0.2
|
||||
case BinaryV02, StructuredV02:
|
||||
return "v0.2"
|
||||
|
||||
// Version 0.3
|
||||
case BinaryV03, StructuredV03, BatchedV03:
|
||||
return "v0.3"
|
||||
|
||||
// Version 1.0
|
||||
case BinaryV1, StructuredV1, BatchedV1:
|
||||
return "v1.0"
|
||||
|
||||
// Unknown
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Codec creates a structured string to represent the the codec version.
|
||||
func (e Encoding) Codec() string {
|
||||
switch e {
|
||||
case Default:
|
||||
return "default"
|
||||
|
||||
// Version 0.1
|
||||
case BinaryV01:
|
||||
return "binary/v0.1"
|
||||
case StructuredV01:
|
||||
return "structured/v0.1"
|
||||
|
||||
// Version 0.2
|
||||
case BinaryV02:
|
||||
return "binary/v0.2"
|
||||
case StructuredV02:
|
||||
return "structured/v0.2"
|
||||
|
||||
// Version 0.3
|
||||
case BinaryV03:
|
||||
return "binary/v0.3"
|
||||
case StructuredV03:
|
||||
return "structured/v0.3"
|
||||
case BatchedV03:
|
||||
return "batched/v0.3"
|
||||
|
||||
// Version 1.0
|
||||
case BinaryV1:
|
||||
return "binary/v1.0"
|
||||
case StructuredV1:
|
||||
return "structured/v1.0"
|
||||
case BatchedV1:
|
||||
return "batched/v1.0"
|
||||
|
||||
// Unknown
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Name creates a string to represent the the codec name.
|
||||
func (e Encoding) Name() string {
|
||||
switch e {
|
||||
case Default:
|
||||
return Binary
|
||||
case BinaryV01, BinaryV02, BinaryV03, BinaryV1:
|
||||
return Binary
|
||||
case StructuredV01, StructuredV02, StructuredV03, StructuredV1:
|
||||
return Structured
|
||||
case BatchedV03, BatchedV1:
|
||||
return Batched
|
||||
default:
|
||||
return Binary
|
||||
}
|
||||
}
|
||||
148
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/message.go
generated
vendored
148
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/message.go
generated
vendored
|
|
@ -1,148 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
)
|
||||
|
||||
// type check that this transport message impl matches the contract
|
||||
var _ transport.Message = (*Message)(nil)
|
||||
|
||||
// Message is an http transport message.
|
||||
type Message struct {
|
||||
Header http.Header
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// Response is an http transport response.
|
||||
type Response struct {
|
||||
StatusCode int
|
||||
Message
|
||||
}
|
||||
|
||||
// CloudEventsVersion inspects a message and tries to discover and return the
|
||||
// CloudEvents spec version.
|
||||
func (m Message) CloudEventsVersion() string {
|
||||
|
||||
// TODO: the impl of this method needs to move into the codec.
|
||||
|
||||
if m.Header != nil {
|
||||
// Try headers first.
|
||||
// v0.1, cased from the spec
|
||||
// Note: don't pass literal string direct to m.Header[] so that
|
||||
// go vet won't complain about non-canonical case.
|
||||
name := "CE-CloudEventsVersion"
|
||||
if v := m.Header[name]; len(v) == 1 {
|
||||
return v[0]
|
||||
}
|
||||
// v0.2, canonical casing
|
||||
if ver := m.Header.Get("CE-CloudEventsVersion"); ver != "" {
|
||||
return ver
|
||||
}
|
||||
|
||||
// v0.2, cased from the spec
|
||||
name = "ce-specversion"
|
||||
if v := m.Header[name]; len(v) == 1 {
|
||||
return v[0]
|
||||
}
|
||||
// v0.2, canonical casing
|
||||
name = "ce-specversion"
|
||||
if ver := m.Header.Get(name); ver != "" {
|
||||
return ver
|
||||
}
|
||||
}
|
||||
|
||||
// Then try the data body.
|
||||
// TODO: we need to use the correct decoding based on content type.
|
||||
|
||||
raw := make(map[string]json.RawMessage)
|
||||
if err := json.Unmarshal(m.Body, &raw); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// v0.1
|
||||
if v, ok := raw["cloudEventsVersion"]; ok {
|
||||
var version string
|
||||
if err := json.Unmarshal(v, &version); err != nil {
|
||||
return ""
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
// v0.2
|
||||
if v, ok := raw["specversion"]; ok {
|
||||
var version string
|
||||
if err := json.Unmarshal(v, &version); err != nil {
|
||||
return ""
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func readAllClose(r io.ReadCloser) ([]byte, error) {
|
||||
if r != nil {
|
||||
defer r.Close()
|
||||
return ioutil.ReadAll(r)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NewMessage creates a new message from the Header and Body of
|
||||
// an http.Request or http.Response
|
||||
func NewMessage(header http.Header, body io.ReadCloser) (*Message, error) {
|
||||
var m Message
|
||||
err := m.Init(header, body)
|
||||
return &m, err
|
||||
}
|
||||
|
||||
// NewResponse creates a new response from the Header and Body of
|
||||
// an http.Request or http.Response
|
||||
func NewResponse(header http.Header, body io.ReadCloser, statusCode int) (*Response, error) {
|
||||
resp := Response{StatusCode: statusCode}
|
||||
err := resp.Init(header, body)
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
// Copy copies a new Body and Header into a message, replacing any previous data.
|
||||
func (m *Message) Init(header http.Header, body io.ReadCloser) error {
|
||||
m.Header = make(http.Header, len(header))
|
||||
copyHeadersEnsure(header, &m.Header)
|
||||
var err error
|
||||
m.Body, err = readAllClose(body)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Message) copyOut(header *http.Header, body *io.ReadCloser) {
|
||||
copyHeadersEnsure(m.Header, header)
|
||||
*body = nil
|
||||
if m.Body != nil {
|
||||
copy := append([]byte(nil), m.Body...)
|
||||
*body = ioutil.NopCloser(bytes.NewBuffer(copy))
|
||||
}
|
||||
}
|
||||
|
||||
// ToRequest updates a http.Request from a Message.
|
||||
// Replaces Body, ContentLength and Method, updates Headers.
|
||||
// Panic if req is nil
|
||||
func (m *Message) ToRequest(req *http.Request) {
|
||||
m.copyOut(&req.Header, &req.Body)
|
||||
req.ContentLength = int64(len(m.Body))
|
||||
req.Method = http.MethodPost
|
||||
}
|
||||
|
||||
// ToResponse updates a http.Response from a Response.
|
||||
// Replaces Body, updates Headers.
|
||||
// Panic if resp is nil
|
||||
func (m *Response) ToResponse(resp *http.Response) {
|
||||
m.copyOut(&resp.Header, &resp.Body)
|
||||
resp.ContentLength = int64(len(m.Body))
|
||||
resp.StatusCode = m.StatusCode
|
||||
}
|
||||
109
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/observability.go
generated
vendored
109
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/observability.go
generated
vendored
|
|
@ -1,109 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyMs measures the latency in milliseconds for the http transport
|
||||
// methods for CloudEvents.
|
||||
LatencyMs = stats.Float64(
|
||||
"cloudevents.io/sdk-go/transport/http/latency",
|
||||
"The latency in milliseconds for the http transport methods for CloudEvents.",
|
||||
"ms")
|
||||
)
|
||||
|
||||
var (
|
||||
// LatencyView is an OpenCensus view that shows http transport method latency.
|
||||
LatencyView = &view.View{
|
||||
Name: "transport/http/latency",
|
||||
Measure: LatencyMs,
|
||||
Description: "The distribution of latency inside of http transport 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 (
|
||||
reportSend observed = iota
|
||||
reportReceive
|
||||
reportServeHTTP
|
||||
reportEncode
|
||||
reportDecode
|
||||
)
|
||||
|
||||
// TraceName implements Observable.TraceName
|
||||
func (o observed) TraceName() string {
|
||||
switch o {
|
||||
case reportSend:
|
||||
return "transport/http/send"
|
||||
case reportReceive:
|
||||
return "transport/http/receive"
|
||||
case reportServeHTTP:
|
||||
return "transport/http/servehttp"
|
||||
case reportEncode:
|
||||
return "transport/http/encode"
|
||||
case reportDecode:
|
||||
return "transport/http/decode"
|
||||
default:
|
||||
return "transport/http/unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
case reportSend:
|
||||
return "send"
|
||||
case reportReceive:
|
||||
return "receive"
|
||||
case reportServeHTTP:
|
||||
return "servehttp"
|
||||
case reportEncode:
|
||||
return "encode"
|
||||
case reportDecode:
|
||||
return "decode"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (o observed) LatencyMs() *stats.Float64Measure {
|
||||
return LatencyMs
|
||||
}
|
||||
|
||||
// CodecObserved is a wrapper to append version to observed.
|
||||
type CodecObserved struct {
|
||||
// Method
|
||||
o observed
|
||||
// Codec
|
||||
c string
|
||||
}
|
||||
|
||||
// Adheres to Observable
|
||||
var _ observability.Observable = (*CodecObserved)(nil)
|
||||
|
||||
// TraceName implements Observable.TraceName
|
||||
func (c CodecObserved) TraceName() string {
|
||||
return fmt.Sprintf("%s/%s", c.o.TraceName(), c.c)
|
||||
}
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (c CodecObserved) MethodName() string {
|
||||
return fmt.Sprintf("%s/%s", c.o.MethodName(), c.c)
|
||||
}
|
||||
|
||||
// LatencyMs implements Observable.LatencyMs
|
||||
func (c CodecObserved) LatencyMs() *stats.Float64Measure {
|
||||
return c.o.LatencyMs()
|
||||
}
|
||||
266
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/options.go
generated
vendored
266
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/options.go
generated
vendored
|
|
@ -1,266 +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(*Transport) error
|
||||
|
||||
// WithTarget sets the outbound recipient of cloudevents when using an HTTP
|
||||
// request.
|
||||
func WithTarget(targetUrl string) Option {
|
||||
return func(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http target option can not set nil transport")
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
if t.Req == nil {
|
||||
t.Req = &nethttp.Request{
|
||||
Method: nethttp.MethodPost,
|
||||
}
|
||||
}
|
||||
t.Req.URL = target
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("http target option was empty string")
|
||||
}
|
||||
}
|
||||
|
||||
// WithMethod sets the outbound recipient of cloudevents when using an HTTP
|
||||
// request.
|
||||
func WithMethod(method string) Option {
|
||||
return func(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http method option can not set nil transport")
|
||||
}
|
||||
method = strings.TrimSpace(method)
|
||||
if method != "" {
|
||||
if t.Req == nil {
|
||||
t.Req = &nethttp.Request{}
|
||||
}
|
||||
t.Req.Method = method
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("http method 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(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http header option can not set nil transport")
|
||||
}
|
||||
key = strings.TrimSpace(key)
|
||||
if key != "" {
|
||||
if t.Req == nil {
|
||||
t.Req = &nethttp.Request{}
|
||||
}
|
||||
if t.Req.Header == nil {
|
||||
t.Req.Header = nethttp.Header{}
|
||||
}
|
||||
t.Req.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 *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http shutdown timeout option can not set nil transport")
|
||||
}
|
||||
t.ShutdownTimeout = &timeout
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithEncoding sets the encoding for clients with HTTP transports.
|
||||
func WithEncoding(encoding Encoding) Option {
|
||||
return func(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http encoding option can not set nil transport")
|
||||
}
|
||||
t.Encoding = encoding
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultEncodingSelector sets the encoding selection strategy for
|
||||
// default encoding selections based on Event.
|
||||
func WithDefaultEncodingSelector(fn EncodingSelector) Option {
|
||||
return func(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http default encoding selector option can not set nil transport")
|
||||
}
|
||||
if fn != nil {
|
||||
t.DefaultEncodingSelectionFn = fn
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("http fn for DefaultEncodingSelector was nil")
|
||||
}
|
||||
}
|
||||
|
||||
// WithContextBasedEncoding sets the encoding selection strategy for
|
||||
// default encoding selections based context and then on Event, the encoded
|
||||
// event will be the given version in the encoding specified by the given
|
||||
// context, or Binary if not set.
|
||||
func WithContextBasedEncoding() Option {
|
||||
return func(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http context based encoding option can not set nil transport")
|
||||
}
|
||||
|
||||
t.DefaultEncodingSelectionFn = ContextBasedEncodingSelectionStrategy
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithBinaryEncoding sets the encoding selection strategy for
|
||||
// default encoding selections based on Event, the encoded event will be the
|
||||
// given version in Binary form.
|
||||
func WithBinaryEncoding() Option {
|
||||
return func(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http binary encoding option can not set nil transport")
|
||||
}
|
||||
|
||||
t.DefaultEncodingSelectionFn = DefaultBinaryEncodingSelectionStrategy
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithStructuredEncoding sets the encoding selection strategy for
|
||||
// default encoding selections based on Event, the encoded event will be the
|
||||
// given version in Structured form.
|
||||
func WithStructuredEncoding() Option {
|
||||
return func(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http structured encoding option can not set nil transport")
|
||||
}
|
||||
|
||||
t.DefaultEncodingSelectionFn = DefaultStructuredEncodingSelectionStrategy
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkListen(t *Transport, 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 *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http port option can not set nil transport")
|
||||
}
|
||||
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 *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http listener option can not set nil transport")
|
||||
}
|
||||
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 *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http path option can not set nil transport")
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http middleware option can not set nil transport")
|
||||
}
|
||||
t.middleware = append(t.middleware, middleware)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLongPollTarget sets the receivers URL to perform long polling after
|
||||
// StartReceiver is called.
|
||||
func WithLongPollTarget(targetUrl string) Option {
|
||||
return func(t *Transport) error {
|
||||
if t == nil {
|
||||
return fmt.Errorf("http long poll target option can not set nil transport")
|
||||
}
|
||||
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 long poll target option failed to parse target url: %s", err.Error())
|
||||
}
|
||||
|
||||
if t.LongPollReq == nil {
|
||||
t.LongPollReq = &nethttp.Request{
|
||||
Method: nethttp.MethodGet,
|
||||
}
|
||||
}
|
||||
t.LongPollReq.URL = target
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("http long poll target option was empty string")
|
||||
}
|
||||
}
|
||||
681
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/transport.go
generated
vendored
681
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/transport.go
generated
vendored
|
|
@ -1,681 +0,0 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
cecontext "github.com/cloudevents/sdk-go/pkg/cloudevents/context"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/transport"
|
||||
)
|
||||
|
||||
// Transport adheres to transport.Transport.
|
||||
var _ transport.Transport = (*Transport)(nil)
|
||||
|
||||
const (
|
||||
// DefaultShutdownTimeout defines the default timeout given to the http.Server when calling Shutdown.
|
||||
DefaultShutdownTimeout = time.Minute * 1
|
||||
|
||||
// TransportName is the name of this transport.
|
||||
TransportName = "HTTP"
|
||||
)
|
||||
|
||||
// Transport acts as both a http client and a http handler.
|
||||
type Transport struct {
|
||||
// The encoding used to select the codec for outbound events.
|
||||
Encoding Encoding
|
||||
|
||||
// DefaultEncodingSelectionFn allows for other encoding selection strategies to be injected.
|
||||
DefaultEncodingSelectionFn EncodingSelector
|
||||
|
||||
// ShutdownTimeout defines the timeout given to the http.Server when calling Shutdown.
|
||||
// If nil, DefaultShutdownTimeout is used.
|
||||
ShutdownTimeout *time.Duration
|
||||
|
||||
// Sending
|
||||
|
||||
// Client is the http client that will be used to send requests.
|
||||
// If nil, the Transport will create a one.
|
||||
Client *http.Client
|
||||
// Req is the base http request that is used for http.Do.
|
||||
// Only .Method, .URL, .Close, and .Header is considered.
|
||||
// If not set, Req.Method defaults to POST.
|
||||
// Req.URL or context.WithTarget(url) are required for sending.
|
||||
Req *http.Request
|
||||
|
||||
// Receiving
|
||||
|
||||
// Receiver is invoked target for incoming events.
|
||||
Receiver transport.Receiver
|
||||
// Converter is invoked if the incoming transport receives an undecodable
|
||||
// message.
|
||||
Converter transport.Converter
|
||||
// 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
|
||||
// Handler is the handler the http Server will use. Use this to reuse the
|
||||
// http server. If nil, the Transport will create a one.
|
||||
Handler *http.ServeMux
|
||||
|
||||
// LongPollClient is the http client that will be used to long poll.
|
||||
// If nil and LongPollReq is set, the Transport will create a one.
|
||||
LongPollClient *http.Client
|
||||
// LongPollReq is the base http request that is used for long poll.
|
||||
// Only .Method, .URL, .Close, and .Header is considered.
|
||||
// If not set, LongPollReq.Method defaults to GET.
|
||||
// LongPollReq.URL or context.WithLongPollTarget(url) are required to long
|
||||
// poll on StartReceiver.
|
||||
LongPollReq *http.Request
|
||||
|
||||
listener net.Listener
|
||||
server *http.Server
|
||||
handlerRegistered bool
|
||||
codec transport.Codec
|
||||
// Create Mutex
|
||||
crMu sync.Mutex
|
||||
// Receive Mutex
|
||||
reMu sync.Mutex
|
||||
|
||||
middleware []Middleware
|
||||
}
|
||||
|
||||
func New(opts ...Option) (*Transport, error) {
|
||||
t := &Transport{
|
||||
Req: &http.Request{
|
||||
Method: http.MethodPost,
|
||||
},
|
||||
}
|
||||
if err := t.applyOptions(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *Transport) applyOptions(opts ...Option) error {
|
||||
for _, fn := range opts {
|
||||
if err := fn(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Transport) loadCodec(ctx context.Context) bool {
|
||||
if t.codec == nil {
|
||||
t.crMu.Lock()
|
||||
if t.DefaultEncodingSelectionFn != nil && t.Encoding != Default {
|
||||
logger := cecontext.LoggerFrom(ctx)
|
||||
logger.Warn("transport has a DefaultEncodingSelectionFn set but Encoding is not Default. DefaultEncodingSelectionFn will be ignored.")
|
||||
|
||||
t.codec = &Codec{
|
||||
Encoding: t.Encoding,
|
||||
}
|
||||
} else {
|
||||
t.codec = &Codec{
|
||||
Encoding: t.Encoding,
|
||||
DefaultEncodingSelectionFn: t.DefaultEncodingSelectionFn,
|
||||
}
|
||||
}
|
||||
t.crMu.Unlock()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Send implements Transport.Send
|
||||
func (t *Transport) Send(ctx context.Context, event cloudevents.Event) (context.Context, *cloudevents.Event, error) {
|
||||
ctx, r := observability.NewReporter(ctx, reportSend)
|
||||
rctx, resp, err := t.obsSend(ctx, event)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return rctx, resp, err
|
||||
}
|
||||
|
||||
func (t *Transport) obsSend(ctx context.Context, event cloudevents.Event) (context.Context, *cloudevents.Event, error) {
|
||||
if t.Client == nil {
|
||||
t.crMu.Lock()
|
||||
t.Client = &http.Client{}
|
||||
t.crMu.Unlock()
|
||||
}
|
||||
|
||||
req := http.Request{
|
||||
Header: HeaderFrom(ctx),
|
||||
}
|
||||
if t.Req != nil {
|
||||
req.Method = t.Req.Method
|
||||
req.URL = t.Req.URL
|
||||
req.Close = t.Req.Close
|
||||
req.Host = t.Req.Host
|
||||
copyHeadersEnsure(t.Req.Header, &req.Header)
|
||||
}
|
||||
|
||||
// Override the default request with target from context.
|
||||
if target := cecontext.TargetFrom(ctx); target != nil {
|
||||
req.URL = target
|
||||
}
|
||||
|
||||
if ok := t.loadCodec(ctx); !ok {
|
||||
return WithTransportContext(ctx, NewTransportContextFromResponse(nil)), nil, fmt.Errorf("unknown encoding set on transport: %d", t.Encoding)
|
||||
}
|
||||
|
||||
msg, err := t.codec.Encode(ctx, event)
|
||||
if err != nil {
|
||||
return WithTransportContext(ctx, NewTransportContextFromResponse(nil)), nil, err
|
||||
}
|
||||
|
||||
if m, ok := msg.(*Message); ok {
|
||||
m.ToRequest(&req)
|
||||
return httpDo(ctx, t.Client, &req, func(resp *http.Response, err error) (context.Context, *cloudevents.Event, error) {
|
||||
rctx := WithTransportContext(ctx, NewTransportContextFromResponse(resp))
|
||||
if err != nil {
|
||||
return rctx, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
respEvent, err := t.MessageToEvent(ctx, &Message{
|
||||
Header: resp.Header,
|
||||
Body: body,
|
||||
})
|
||||
if err != nil {
|
||||
isErr := true
|
||||
handled := false
|
||||
if txerr, ok := err.(*transport.ErrTransportMessageConversion); ok {
|
||||
if !txerr.IsFatal() {
|
||||
isErr = false
|
||||
}
|
||||
if txerr.Handled() {
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
if isErr {
|
||||
return rctx, nil, err
|
||||
}
|
||||
if handled {
|
||||
return rctx, nil, nil
|
||||
}
|
||||
}
|
||||
if accepted(resp) {
|
||||
return rctx, respEvent, nil
|
||||
}
|
||||
return rctx, respEvent, fmt.Errorf("error sending cloudevent: %s", resp.Status)
|
||||
})
|
||||
}
|
||||
return WithTransportContext(ctx, NewTransportContextFromResponse(nil)), nil, fmt.Errorf("failed to encode Event into a Message")
|
||||
}
|
||||
|
||||
func (t *Transport) MessageToEvent(ctx context.Context, msg *Message) (*cloudevents.Event, error) {
|
||||
logger := cecontext.LoggerFrom(ctx)
|
||||
var event *cloudevents.Event
|
||||
var err error
|
||||
|
||||
if msg.CloudEventsVersion() != "" {
|
||||
// This is likely a cloudevents encoded message, try to decode it.
|
||||
if ok := t.loadCodec(ctx); !ok {
|
||||
err = transport.NewErrTransportMessageConversion("http", fmt.Sprintf("unknown encoding set on transport: %d", t.Encoding), false, true)
|
||||
logger.Error("failed to load codec", zap.Error(err))
|
||||
} else {
|
||||
event, err = t.codec.Decode(ctx, msg)
|
||||
}
|
||||
} else {
|
||||
err = transport.NewErrTransportMessageConversion("http", "cloudevents version unknown", false, false)
|
||||
}
|
||||
|
||||
// If codec returns and error, or could not load the correct codec, try
|
||||
// with the converter if it is set.
|
||||
if err != nil && t.HasConverter() {
|
||||
event, err = t.Converter.Convert(ctx, msg, err)
|
||||
}
|
||||
|
||||
// If err is still set, it means that there was no converter, or the
|
||||
// converter failed to convert.
|
||||
if err != nil {
|
||||
logger.Debug("failed to decode message", zap.Error(err))
|
||||
}
|
||||
|
||||
// If event and error are both nil, then there is nothing to do with this event, it was handled.
|
||||
if err == nil && event == nil {
|
||||
logger.Debug("convert function returned (nil, nil)")
|
||||
err = transport.NewErrTransportMessageConversion("http", "convert function handled request", true, false)
|
||||
}
|
||||
|
||||
return event, err
|
||||
}
|
||||
|
||||
// SetReceiver implements Transport.SetReceiver
|
||||
func (t *Transport) SetReceiver(r transport.Receiver) {
|
||||
t.Receiver = r
|
||||
}
|
||||
|
||||
// SetConverter implements Transport.SetConverter
|
||||
func (t *Transport) SetConverter(c transport.Converter) {
|
||||
t.Converter = c
|
||||
}
|
||||
|
||||
// HasConverter implements Transport.HasConverter
|
||||
func (t *Transport) HasConverter() bool {
|
||||
return t.Converter != nil
|
||||
}
|
||||
|
||||
// StartReceiver implements Transport.StartReceiver
|
||||
// NOTE: This is a blocking call.
|
||||
func (t *Transport) StartReceiver(ctx context.Context) error {
|
||||
t.reMu.Lock()
|
||||
defer t.reMu.Unlock()
|
||||
|
||||
if t.LongPollReq != nil {
|
||||
go func() { _ = t.longPollStart(ctx) }()
|
||||
}
|
||||
|
||||
if t.Handler == nil {
|
||||
t.Handler = http.NewServeMux()
|
||||
}
|
||||
if !t.handlerRegistered {
|
||||
// handler.Handle might panic if the user tries to use the same path as the sdk.
|
||||
t.Handler.Handle(t.GetPath(), t)
|
||||
t.handlerRegistered = true
|
||||
}
|
||||
|
||||
addr, err := t.listen()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.server = &http.Server{
|
||||
Addr: addr.String(),
|
||||
Handler: attachMiddleware(t.Handler, t.middleware),
|
||||
}
|
||||
|
||||
// Shutdown
|
||||
defer func() {
|
||||
t.server.Close()
|
||||
t.server = nil
|
||||
}()
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
errChan <- t.server.Serve(t.listener)
|
||||
}()
|
||||
|
||||
// wait for the server to return or ctx.Done().
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Try a gracefully shutdown.
|
||||
timeout := DefaultShutdownTimeout
|
||||
if t.ShutdownTimeout != nil {
|
||||
timeout = *t.ShutdownTimeout
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
err := t.server.Shutdown(ctx)
|
||||
<-errChan // Wait for server goroutine to exit
|
||||
return err
|
||||
case err := <-errChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) longPollStart(ctx context.Context) error {
|
||||
logger := cecontext.LoggerFrom(ctx)
|
||||
logger.Info("starting long poll receiver")
|
||||
|
||||
if t.LongPollClient == nil {
|
||||
t.crMu.Lock()
|
||||
t.LongPollClient = &http.Client{}
|
||||
t.crMu.Unlock()
|
||||
}
|
||||
req := &http.Request{
|
||||
// TODO: decide if it is ok to use HeaderFrom context here.
|
||||
Header: HeaderFrom(ctx),
|
||||
}
|
||||
if t.LongPollReq != nil {
|
||||
req.Method = t.LongPollReq.Method
|
||||
req.URL = t.LongPollReq.URL
|
||||
req.Close = t.LongPollReq.Close
|
||||
copyHeaders(t.LongPollReq.Header, req.Header)
|
||||
}
|
||||
|
||||
// Override the default request with target from context.
|
||||
if target := LongPollTargetFrom(ctx); target != nil {
|
||||
req.URL = target
|
||||
}
|
||||
|
||||
if req.URL == nil {
|
||||
return errors.New("no long poll target found")
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
msgCh := make(chan Message)
|
||||
defer close(msgCh)
|
||||
|
||||
go func(ch chan<- Message) {
|
||||
for {
|
||||
if resp, err := t.LongPollClient.Do(req); err != nil {
|
||||
logger.Errorw("long poll request returned error", err)
|
||||
uErr := err.(*url.Error)
|
||||
if uErr.Temporary() || uErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
// TODO: if the transport is throwing errors, we might want to try again. Maybe with a back-off sleep.
|
||||
// But this error also might be that there was a done on the context.
|
||||
} else if resp.StatusCode == http.StatusNotModified {
|
||||
// Keep polling.
|
||||
continue
|
||||
} else if resp.StatusCode == http.StatusOK {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
if err := resp.Body.Close(); err != nil {
|
||||
logger.Warnw("error closing long poll response body", zap.Error(err))
|
||||
}
|
||||
msg := Message{
|
||||
Header: resp.Header,
|
||||
Body: body,
|
||||
}
|
||||
msgCh <- msg
|
||||
} else {
|
||||
// TODO: not sure what to do with upstream errors yet.
|
||||
logger.Errorw("unhandled long poll response", zap.Any("resp", resp))
|
||||
}
|
||||
}
|
||||
}(msgCh)
|
||||
|
||||
// Attach the long poll request context to the context.
|
||||
ctx = WithTransportContext(ctx, TransportContext{
|
||||
URI: req.URL.RequestURI(),
|
||||
Host: req.URL.Host,
|
||||
Method: req.Method,
|
||||
})
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case msg := <-msgCh:
|
||||
logger.Debug("got a message", zap.Any("msg", msg))
|
||||
if event, err := t.MessageToEvent(ctx, &msg); err != nil {
|
||||
logger.Errorw("could not convert http message to event", zap.Error(err))
|
||||
} else {
|
||||
logger.Debugw("got an event", zap.Any("event", event))
|
||||
// TODO: deliver event.
|
||||
if _, err := t.invokeReceiver(ctx, *event); err != nil {
|
||||
logger.Errorw("could not invoke receiver event", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type eventError struct {
|
||||
ctx context.Context
|
||||
event *cloudevents.Event
|
||||
err error
|
||||
}
|
||||
|
||||
func httpDo(ctx context.Context, client *http.Client, req *http.Request, fn func(*http.Response, error) (context.Context, *cloudevents.Event, error)) (context.Context, *cloudevents.Event, error) {
|
||||
// Run the HTTP request in a goroutine and pass the response to fn.
|
||||
c := make(chan eventError, 1)
|
||||
req = req.WithContext(ctx)
|
||||
go func() {
|
||||
rctx, event, err := fn(client.Do(req))
|
||||
c <- eventError{ctx: rctx, event: event, err: err}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx, nil, ctx.Err()
|
||||
case ee := <-c:
|
||||
return ee.ctx, ee.event, ee.err
|
||||
}
|
||||
}
|
||||
|
||||
// accepted is a helper method to understand if the response from the target
|
||||
// accepted the CloudEvent.
|
||||
func accepted(resp *http.Response) bool {
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Transport) invokeReceiver(ctx context.Context, event cloudevents.Event) (*Response, error) {
|
||||
ctx, r := observability.NewReporter(ctx, reportReceive)
|
||||
resp, err := t.obsInvokeReceiver(ctx, event)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (t *Transport) obsInvokeReceiver(ctx context.Context, event cloudevents.Event) (*Response, error) {
|
||||
logger := cecontext.LoggerFrom(ctx)
|
||||
if t.Receiver != nil {
|
||||
// Note: http does not use eventResp.Reason
|
||||
eventResp := cloudevents.EventResponse{}
|
||||
resp := Response{}
|
||||
|
||||
err := t.Receiver.Receive(ctx, event, &eventResp)
|
||||
if err != nil {
|
||||
logger.Warnw("got an error from receiver fn", zap.Error(err))
|
||||
resp.StatusCode = http.StatusInternalServerError
|
||||
return &resp, err
|
||||
}
|
||||
|
||||
if eventResp.Event != nil {
|
||||
if t.loadCodec(ctx) {
|
||||
if m, err := t.codec.Encode(ctx, *eventResp.Event); err != nil {
|
||||
logger.Errorw("failed to encode response from receiver fn", zap.Error(err))
|
||||
} else if msg, ok := m.(*Message); ok {
|
||||
resp.Message = *msg
|
||||
}
|
||||
} else {
|
||||
logger.Error("failed to load codec")
|
||||
resp.StatusCode = http.StatusInternalServerError
|
||||
return &resp, err
|
||||
}
|
||||
// Look for a transport response context
|
||||
var trx *TransportResponseContext
|
||||
if ptrTrx, ok := eventResp.Context.(*TransportResponseContext); ok {
|
||||
// found a *TransportResponseContext, use it.
|
||||
trx = ptrTrx
|
||||
} else if realTrx, ok := eventResp.Context.(TransportResponseContext); ok {
|
||||
// found a TransportResponseContext, make it a pointer.
|
||||
trx = &realTrx
|
||||
}
|
||||
// If we found a TransportResponseContext, use it.
|
||||
if trx != nil && trx.Header != nil && len(trx.Header) > 0 {
|
||||
copyHeadersEnsure(trx.Header, &resp.Message.Header)
|
||||
}
|
||||
}
|
||||
|
||||
if eventResp.Status != 0 {
|
||||
resp.StatusCode = eventResp.Status
|
||||
} else {
|
||||
resp.StatusCode = http.StatusAccepted // default is 202 - Accepted
|
||||
}
|
||||
return &resp, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler
|
||||
func (t *Transport) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, r := observability.NewReporter(req.Context(), reportServeHTTP)
|
||||
// Add the transport context to ctx.
|
||||
ctx = WithTransportContext(ctx, NewTransportContext(req))
|
||||
logger := cecontext.LoggerFrom(ctx)
|
||||
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
logger.Errorw("failed to handle request", zap.Error(err))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(`{"error":"Invalid request"}`))
|
||||
r.Error()
|
||||
return
|
||||
}
|
||||
|
||||
event, err := t.MessageToEvent(ctx, &Message{
|
||||
Header: req.Header,
|
||||
Body: body,
|
||||
})
|
||||
if err != nil {
|
||||
isFatal := true
|
||||
handled := false
|
||||
if txerr, ok := err.(*transport.ErrTransportMessageConversion); ok {
|
||||
isFatal = txerr.IsFatal()
|
||||
handled = txerr.Handled()
|
||||
}
|
||||
if isFatal {
|
||||
logger.Errorw("failed to convert http message to event", zap.Error(err))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(fmt.Sprintf(`{"error":%q}`, err.Error())))
|
||||
r.Error()
|
||||
return
|
||||
}
|
||||
// if handled, do not pass to receiver.
|
||||
if handled {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
r.OK()
|
||||
return
|
||||
}
|
||||
}
|
||||
if event == nil {
|
||||
logger.Error("failed to get non-nil event from MessageToEvent")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
r.Error()
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := t.invokeReceiver(ctx, *event)
|
||||
if err != nil {
|
||||
logger.Warnw("error returned from invokeReceiver", zap.Error(err))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(fmt.Sprintf(`{"error":%q}`, err.Error())))
|
||||
r.Error()
|
||||
return
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
if t.Req != nil {
|
||||
copyHeaders(t.Req.Header, w.Header())
|
||||
}
|
||||
if len(resp.Message.Header) > 0 {
|
||||
copyHeaders(resp.Message.Header, w.Header())
|
||||
}
|
||||
|
||||
status := http.StatusAccepted
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 600 {
|
||||
status = resp.StatusCode
|
||||
}
|
||||
w.Header().Add("Content-Length", strconv.Itoa(len(resp.Message.Body)))
|
||||
w.WriteHeader(status)
|
||||
|
||||
if len(resp.Message.Body) > 0 {
|
||||
if _, err := w.Write(resp.Message.Body); err != nil {
|
||||
r.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.OK()
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
r.OK()
|
||||
}
|
||||
|
||||
// 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 (t *Transport) GetPort() int {
|
||||
// Ensure we have a listener and therefore a port.
|
||||
if _, err := t.listen(); err == nil || t.Port != nil {
|
||||
return *t.Port
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (t *Transport) setPort(port int) {
|
||||
if t.Port == nil {
|
||||
t.Port = new(int)
|
||||
}
|
||||
*t.Port = port
|
||||
}
|
||||
|
||||
// listen if not already listening, update t.Port
|
||||
func (t *Transport) listen() (net.Addr, error) {
|
||||
if t.listener == nil {
|
||||
port := 8080
|
||||
if t.Port != nil {
|
||||
port = *t.Port
|
||||
if port < 0 || port > 65535 {
|
||||
return nil, fmt.Errorf("invalid port %d", port)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if t.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", port)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
addr := t.listener.Addr()
|
||||
if tcpAddr, ok := addr.(*net.TCPAddr); ok {
|
||||
t.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 (t *Transport) GetPath() string {
|
||||
path := strings.TrimSpace(t.Path)
|
||||
if len(path) > 0 {
|
||||
return path
|
||||
}
|
||||
return "/" // default
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package transport
|
||||
|
||||
// Message is the abstract transport message wrapper.
|
||||
type Message interface {
|
||||
// CloudEventsVersion returns the version of the CloudEvent.
|
||||
CloudEventsVersion() string
|
||||
|
||||
// TODO maybe get encoding
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
)
|
||||
|
||||
// Transport is the interface for transport sender to send the converted Message
|
||||
// over the underlying transport.
|
||||
type Transport interface {
|
||||
Send(context.Context, cloudevents.Event) (context.Context, *cloudevents.Event, error)
|
||||
|
||||
SetReceiver(Receiver)
|
||||
StartReceiver(context.Context) error
|
||||
|
||||
// SetConverter sets the delegate to use for converting messages that have
|
||||
// failed to be decoded from known codecs for this transport.
|
||||
SetConverter(Converter)
|
||||
// HasConverter is true when a non-nil converter has been set.
|
||||
HasConverter() bool
|
||||
}
|
||||
|
||||
// Receiver is an interface to define how a transport will invoke a listener
|
||||
// of incoming events.
|
||||
type Receiver interface {
|
||||
Receive(context.Context, cloudevents.Event, *cloudevents.EventResponse) error
|
||||
}
|
||||
|
||||
// ReceiveFunc wraps a function as a Receiver object.
|
||||
type ReceiveFunc func(ctx context.Context, e cloudevents.Event, er *cloudevents.EventResponse) error
|
||||
|
||||
// Receive implements Receiver.Receive
|
||||
func (f ReceiveFunc) Receive(ctx context.Context, e cloudevents.Event, er *cloudevents.EventResponse) error {
|
||||
return f(ctx, e, er)
|
||||
}
|
||||
|
||||
// Converter is an interface to define how a transport delegate to convert an
|
||||
// non-understood transport message from the internal codecs. Providing a
|
||||
// Converter allows incoming requests to be bridged to CloudEvents format if
|
||||
// they have not been sent as an event in CloudEvents format.
|
||||
type Converter interface {
|
||||
Convert(context.Context, Message, error) (*cloudevents.Event, error)
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// URLRef 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 URLRef object is
|
||||
// is a flat string.
|
||||
//
|
||||
// deprecated: use URIRef.
|
||||
type URLRef struct {
|
||||
url.URL
|
||||
}
|
||||
|
||||
// ParseURLRef attempts to parse the given string as a URI-Reference.
|
||||
func ParseURLRef(u string) *URLRef {
|
||||
if u == "" {
|
||||
return nil
|
||||
}
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &URLRef{URL: *pu}
|
||||
}
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
// marshaled using json.Marshal.
|
||||
func (u URLRef) 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 *URLRef) UnmarshalJSON(b []byte) error {
|
||||
var ref string
|
||||
if err := json.Unmarshal(b, &ref); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURLRef(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 URLRef) 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 *URLRef) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var ref string
|
||||
if err := d.DecodeElement(&ref, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURLRef(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the full string representation of the URI-Reference.
|
||||
func (u *URLRef) String() string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
return u.URL.String()
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
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()
|
||||
|
||||
// Results
|
||||
|
||||
ResultIs = protocol.ResultIs
|
||||
ResultAs = protocol.ResultAs
|
||||
|
||||
// Receipt helpers
|
||||
|
||||
NewReceipt = protocol.NewReceipt
|
||||
|
||||
ResultACK = protocol.ResultACK
|
||||
ResultNACK = protocol.ResultNACK
|
||||
|
||||
IsACK = protocol.IsACK
|
||||
IsNACK = protocol.IsNACK
|
||||
|
||||
// Event Creation
|
||||
|
||||
NewEvent = event.New
|
||||
NewResult = protocol.NewResult
|
||||
|
||||
NewHTTPResult = http.NewResult
|
||||
|
||||
// Message Creation
|
||||
|
||||
ToMessage = binding.ToMessage
|
||||
|
||||
// HTTP Messages
|
||||
|
||||
WriteHTTPRequest = http.WriteRequest
|
||||
|
||||
// Tracing
|
||||
|
||||
EnableTracing = observability.EnableTracing
|
||||
|
||||
// Context
|
||||
|
||||
ContextWithTarget = context.WithTarget
|
||||
TargetFromContext = context.TargetFrom
|
||||
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
|
||||
)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/binding/spec"
|
||||
)
|
||||
|
||||
// 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 are invoked every time this BinaryWriter implementation is used to visit a Message
|
||||
type BinaryWriter interface {
|
||||
// Method invoked at the beginning of the visit. Useful to perform initial memory allocations
|
||||
Start(ctx context.Context) error
|
||||
|
||||
// Set a standard attribute.
|
||||
//
|
||||
// The value can either be the correct golang type for the attribute, or a canonical
|
||||
// string encoding. 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. See package types to perform the needed conversions
|
||||
SetExtension(name string, value interface{}) 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
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package binding
|
||||
|
||||
/*
|
||||
|
||||
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 module 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.
|
||||
|
||||
*/
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
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
|
||||
)
|
||||
|
||||
// Error to specify that or 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")
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
FORMAT_EVENT_STRUCTURED = "FORMAT_EVENT_STRUCTURED"
|
||||
)
|
||||
|
||||
// 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, FORMAT_EVENT_STRUCTURED, 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 = b.Start(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 b.End(ctx)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Configure 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, FORMAT_EVENT_STRUCTURED, f)
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package binding
|
||||
|
||||
type finishMessage struct {
|
||||
Message
|
||||
finish func(error)
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package format
|
||||
|
||||
/*
|
||||
Package format formats structured events.
|
||||
|
||||
The "application/cloudevents+json" format is built-in and always
|
||||
available. Other formats may be added.
|
||||
*/
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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 mediaType, or nil if not found.
|
||||
func Lookup(mediaType string) Format { 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)
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package binding
|
||||
|
||||
import "context"
|
||||
|
||||
// The ReadStructured and ReadBinary methods allows to perform an optimized encoding of a Message to a specific data structure.
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// Message Wrapper interface is used to walk through a decorated Message and unwrap it.
|
||||
type MessageWrapper interface {
|
||||
Message
|
||||
|
||||
// Method to get the wrapped message
|
||||
GetWrappedMessage() Message
|
||||
}
|
||||
136
vendor/github.com/cloudevents/sdk-go/v2/binding/spec/attributes.go
generated
vendored
Normal file
136
vendor/github.com/cloudevents/sdk-go/v2/binding/spec/attributes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
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{})
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package spec
|
||||
|
||||
/*
|
||||
Package spec provides spec-version metadata.
|
||||
|
||||
For use by code that maps events using (prefixed) attribute name strings.
|
||||
Supports handling multiple spec versions uniformly.
|
||||
|
||||
*/
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
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")
|
||||
}
|
||||
17
vendor/github.com/cloudevents/sdk-go/v2/binding/structured_writer.go
generated
vendored
Normal file
17
vendor/github.com/cloudevents/sdk-go/v2/binding/structured_writer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
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"
|
||||
)
|
||||
|
||||
// Generic error when a conversion of a Message to an Event fails
|
||||
var ErrCannotConvertToEvent = errors.New("cannot convert message to event")
|
||||
|
||||
// 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 ...TransformerFactory) (*event.Event, error) {
|
||||
if message == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
messageEncoding := message.ReadEncoding()
|
||||
if messageEncoding == EncodingEvent {
|
||||
m := message
|
||||
for m != nil {
|
||||
if em, ok := m.(*EventMessage); ok {
|
||||
e := (*event.Event)(em)
|
||||
var tf TransformerFactories
|
||||
tf = transformers
|
||||
if err := tf.EventTransformer()(e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
if mw, ok := m.(MessageWrapper); ok {
|
||||
m = mw.GetWrappedMessage()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil, ErrCannotConvertToEvent
|
||||
}
|
||||
|
||||
e := event.New()
|
||||
encoder := &messageToEventBuilder{event: &e}
|
||||
if _, err := DirectWrite(
|
||||
context.Background(),
|
||||
message,
|
||||
encoder,
|
||||
encoder,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tf TransformerFactories
|
||||
tf = transformers
|
||||
if err := tf.EventTransformer()(&e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &e, nil
|
||||
}
|
||||
|
||||
type messageToEventBuilder struct {
|
||||
event *event.Event
|
||||
}
|
||||
|
||||
var _ StructuredWriter = (*messageToEventBuilder)(nil)
|
||||
var _ BinaryWriter = (*messageToEventBuilder)(nil)
|
||||
|
||||
func (b *messageToEventBuilder) SetStructuredEvent(ctx context.Context, format format.Format, event io.Reader) error {
|
||||
var buf bytes.Buffer
|
||||
_, err := io.Copy(&buf, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return format.Unmarshal(buf.Bytes(), b.event)
|
||||
}
|
||||
|
||||
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.event.DataEncoded = buf.Bytes()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *messageToEventBuilder) SetAttribute(attribute spec.Attribute, value interface{}) error {
|
||||
// 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.event.Context = b.event.Context.AsV03()
|
||||
case event.CloudEventsVersionV1:
|
||||
b.event.Context = b.event.Context.AsV1()
|
||||
default:
|
||||
return fmt.Errorf("unrecognized event version %s", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return attribute.Set(b.event.Context, value)
|
||||
}
|
||||
|
||||
func (b *messageToEventBuilder) SetExtension(name string, value interface{}) error {
|
||||
value, err := types.Validate(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.event.SetExtension(name, value)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
)
|
||||
|
||||
// Implements a transformation process while transferring the event from the Message implementation
|
||||
// to the provided encoder
|
||||
//
|
||||
// A transformer could optionally not provide an implementation for binary and/or structured encodings,
|
||||
// returning nil to the respective factory method.
|
||||
type TransformerFactory interface {
|
||||
// Can return nil if the transformation doesn't support structured encoding directly
|
||||
StructuredTransformer(writer StructuredWriter) StructuredWriter
|
||||
|
||||
// Can return nil if the transformation doesn't support binary encoding directly
|
||||
BinaryTransformer(writer BinaryWriter) BinaryWriter
|
||||
|
||||
// Can return nil if the transformation doesn't support events
|
||||
EventTransformer() EventTransformer
|
||||
}
|
||||
|
||||
// Utility type alias to manage multiple TransformerFactory
|
||||
type TransformerFactories []TransformerFactory
|
||||
|
||||
func (t TransformerFactories) StructuredTransformer(writer StructuredWriter) StructuredWriter {
|
||||
if writer == nil {
|
||||
return nil
|
||||
}
|
||||
res := writer
|
||||
for _, b := range t {
|
||||
if r := b.StructuredTransformer(res); r != nil {
|
||||
res = r
|
||||
} else {
|
||||
return nil // Structured not supported!
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (t TransformerFactories) BinaryTransformer(writer BinaryWriter) BinaryWriter {
|
||||
if writer == nil {
|
||||
return nil
|
||||
}
|
||||
res := writer
|
||||
for i := range t {
|
||||
b := t[len(t)-i-1]
|
||||
if r := b.BinaryTransformer(res); r != nil {
|
||||
res = r
|
||||
} else {
|
||||
return nil // Binary not supported!
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (t TransformerFactories) EventTransformer() EventTransformer {
|
||||
return func(e *event.Event) error {
|
||||
for _, b := range t {
|
||||
f := b.EventTransformer()
|
||||
if f != nil {
|
||||
err := f(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// EventTransformer mutates the provided Event
|
||||
type EventTransformer func(*event.Event) error
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package binding
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
)
|
||||
|
||||
const (
|
||||
SKIP_DIRECT_STRUCTURED_ENCODING = "SKIP_DIRECT_STRUCTURED_ENCODING"
|
||||
SKIP_DIRECT_BINARY_ENCODING = "SKIP_DIRECT_BINARY_ENCODING"
|
||||
PREFERRED_EVENT_ENCODING = "PREFERRED_EVENT_ENCODING"
|
||||
)
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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 ...TransformerFactory,
|
||||
) (Encoding, error) {
|
||||
if structuredWriter != nil && !GetOrDefaultFromCtx(ctx, SKIP_DIRECT_STRUCTURED_ENCODING, false).(bool) {
|
||||
// Wrap the transformers in the structured builder
|
||||
structuredWriter = TransformerFactories(transformers).StructuredTransformer(structuredWriter)
|
||||
|
||||
// StructuredTransformer could return nil if one of transcoders doesn't support
|
||||
// direct structured transcoding
|
||||
if structuredWriter != nil {
|
||||
if err := message.ReadStructured(ctx, structuredWriter); err == nil {
|
||||
return EncodingStructured, nil
|
||||
} else if err != ErrNotStructured {
|
||||
return EncodingStructured, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if binaryWriter != nil && !GetOrDefaultFromCtx(ctx, SKIP_DIRECT_BINARY_ENCODING, false).(bool) {
|
||||
binaryWriter = TransformerFactories(transformers).BinaryTransformer(binaryWriter)
|
||||
if binaryWriter != nil {
|
||||
if err := message.ReadBinary(ctx, binaryWriter); err == nil {
|
||||
return EncodingBinary, nil
|
||||
} else if err != ErrNotBinary {
|
||||
return EncodingBinary, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EncodingUnknown, ErrUnknownEncoding
|
||||
}
|
||||
|
||||
// This is 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 ...TransformerFactory,
|
||||
) (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, PREFERRED_EVENT_ENCODING, EncodingBinary).(Encoding) == EncodingStructured {
|
||||
if structuredWriter != nil {
|
||||
return EncodingStructured, message.ReadStructured(ctx, structuredWriter)
|
||||
}
|
||||
if binaryWriter != nil {
|
||||
return EncodingBinary, message.ReadBinary(ctx, binaryWriter)
|
||||
}
|
||||
} else {
|
||||
if binaryWriter != nil {
|
||||
return EncodingBinary, message.ReadBinary(ctx, binaryWriter)
|
||||
}
|
||||
if structuredWriter != nil {
|
||||
return EncodingStructured, message.ReadStructured(ctx, structuredWriter)
|
||||
}
|
||||
}
|
||||
|
||||
return EncodingUnknown, ErrUnknownEncoding
|
||||
}
|
||||
|
||||
// Skip direct structured to structured encoding during the encoding process
|
||||
func WithSkipDirectStructuredEncoding(ctx context.Context, skip bool) context.Context {
|
||||
return context.WithValue(ctx, SKIP_DIRECT_STRUCTURED_ENCODING, skip)
|
||||
}
|
||||
|
||||
// Skip direct binary to binary encoding during the encoding process
|
||||
func WithSkipDirectBinaryEncoding(ctx context.Context, skip bool) context.Context {
|
||||
return context.WithValue(ctx, SKIP_DIRECT_BINARY_ENCODING, skip)
|
||||
}
|
||||
|
||||
// Define the preferred encoding from event to message during the encoding process
|
||||
func WithPreferredEventEncoding(ctx context.Context, enc Encoding) context.Context {
|
||||
return context.WithValue(ctx, PREFERRED_EVENT_ENCODING, enc)
|
||||
}
|
||||
|
||||
// Force structured encoding during the encoding process
|
||||
func WithForceStructured(ctx context.Context) context.Context {
|
||||
return context.WithValue(context.WithValue(ctx, PREFERRED_EVENT_ENCODING, EncodingStructured), SKIP_DIRECT_BINARY_ENCODING, true)
|
||||
}
|
||||
|
||||
// Force binary encoding during the encoding process
|
||||
func WithForceBinary(ctx context.Context) context.Context {
|
||||
return context.WithValue(context.WithValue(ctx, PREFERRED_EVENT_ENCODING, EncodingBinary), SKIP_DIRECT_STRUCTURED_ENCODING, true)
|
||||
}
|
||||
|
||||
// Get a configuration value from the provided context
|
||||
func GetOrDefaultFromCtx(ctx context.Context, key string, def interface{}) interface{} {
|
||||
if val := ctx.Value(key); val != nil {
|
||||
return val
|
||||
} else {
|
||||
return def
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
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 := c.invoker.Invoke(ctx, msg, respFn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
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
|
||||
}
|
||||
101
vendor/github.com/cloudevents/sdk-go/v2/client/client_observed.go
generated
vendored
Normal file
101
vendor/github.com/cloudevents/sdk-go/v2/client/client_observed.go
generated
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
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"
|
||||
)
|
||||
|
||||
// New 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
|
||||
}
|
||||
|
|
@ -4,17 +4,18 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents"
|
||||
"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 cloudevents.Event) cloudevents.Event
|
||||
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 cloudevents.Event) cloudevents.Event {
|
||||
func DefaultIDToUUIDIfNotSet(ctx context.Context, event event.Event) event.Event {
|
||||
if event.Context != nil {
|
||||
if event.ID() == "" {
|
||||
event.Context = event.Context.Clone()
|
||||
|
|
@ -26,7 +27,7 @@ func DefaultIDToUUIDIfNotSet(ctx context.Context, event cloudevents.Event) cloud
|
|||
|
||||
// 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 cloudevents.Event) cloudevents.Event {
|
||||
func DefaultTimeToNowIfNotSet(ctx context.Context, event event.Event) event.Event {
|
||||
if event.Context != nil {
|
||||
if event.Time().IsZero() {
|
||||
event.Context = event.Context.Clone()
|
||||
|
|
@ -35,3 +36,17 @@ func DefaultTimeToNowIfNotSet(ctx context.Context, event cloudevents.Event) clou
|
|||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
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 {
|
||||
// TODO
|
||||
} else if err := r.invoker.Invoke(ctx, msg, respFn); err != nil {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
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) {
|
||||
defer func() {
|
||||
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))
|
||||
}
|
||||
}
|
||||
if respFn != nil {
|
||||
var rm binding.Message
|
||||
if resp != nil {
|
||||
rm = (*binding.EventMessage)(resp)
|
||||
}
|
||||
|
||||
return respFn(ctx, rm, result) // TODO: there is a chance this never gets called. Is that ok?
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *receiveInvoker) IsReceiver() bool {
|
||||
return !r.fn.hasEventOut
|
||||
}
|
||||
|
||||
func (r *receiveInvoker) IsResponder() bool {
|
||||
return r.fn.hasEventOut
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
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"
|
||||
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(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
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/event"
|
||||
"github.com/cloudevents/sdk-go/v2/protocol"
|
||||
)
|
||||
|
||||
// Receive 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
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package context
|
|||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Opaque key type used to store target
|
||||
|
|
@ -51,26 +50,3 @@ func TopicFrom(ctx context.Context) string {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Opaque key type used to store encoding
|
||||
type encodingKeyType struct{}
|
||||
|
||||
var encodingKey = encodingKeyType{}
|
||||
|
||||
// WithEncoding returns back a new context with the given encoding. Encoding is intended to be transport dependent.
|
||||
// For http transport, `encoding` should be one of [binary, structured] and will be used to override the outbound
|
||||
// codec encoding setting. If the transport does not understand the encoding, it will be ignored.
|
||||
func WithEncoding(ctx context.Context, encoding string) context.Context {
|
||||
return context.WithValue(ctx, encodingKey, strings.ToLower(encoding))
|
||||
}
|
||||
|
||||
// EncodingFrom looks in the given context and returns `target` as a parsed url if found and valid, otherwise nil.
|
||||
func EncodingFrom(ctx context.Context) string {
|
||||
c := ctx.Value(encodingKey)
|
||||
if c != nil {
|
||||
if s, ok := c.(string); ok && s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
const (
|
||||
TextPlain = "text/plain"
|
||||
TextJSON = "text/json"
|
||||
ApplicationJSON = "application/json"
|
||||
ApplicationXML = "application/xml"
|
||||
|
|
@ -20,6 +21,12 @@ func StringOfApplicationXML() *string {
|
|||
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 {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
const (
|
||||
Base64 = "base64"
|
||||
|
|
@ -4,16 +4,15 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/json"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/text"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/xml"
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"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`. What
|
||||
// `in` is could be decoder dependent. For example, `in` could be bytes, or a
|
||||
// base64 string.
|
||||
type Decoder func(ctx context.Context, in, out interface{}) error
|
||||
// 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`.
|
||||
|
|
@ -56,18 +55,7 @@ func AddEncoder(contentType string, fn Encoder) {
|
|||
// 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, out interface{}) error {
|
||||
_, r := observability.NewReporter(ctx, reportDecode)
|
||||
err := obsDecode(ctx, contentType, in, out)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func obsDecode(ctx context.Context, contentType string, in, out interface{}) error {
|
||||
func Decode(ctx context.Context, contentType string, in []byte, out interface{}) error {
|
||||
if fn, ok := decoder[contentType]; ok {
|
||||
return fn(ctx, in, out)
|
||||
}
|
||||
|
|
@ -78,17 +66,6 @@ func obsDecode(ctx context.Context, contentType string, in, out interface{}) err
|
|||
// 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) {
|
||||
_, r := observability.NewReporter(ctx, reportEncode)
|
||||
b, err := obsEncode(ctx, contentType, in)
|
||||
if err != nil {
|
||||
r.Error()
|
||||
} else {
|
||||
r.OK()
|
||||
}
|
||||
return b, err
|
||||
}
|
||||
|
||||
func obsEncode(ctx context.Context, contentType string, in interface{}) ([]byte, error) {
|
||||
if fn, ok := encoder[contentType]; ok {
|
||||
return fn(ctx, in)
|
||||
}
|
||||
50
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/codec_observed.go
generated
vendored
Normal file
50
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/codec_observed.go
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
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
|
||||
}
|
||||
51
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/json/data.go
generated
vendored
Normal file
51
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/json/data.go
generated
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
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)
|
||||
}
|
||||
30
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/json/data_observed.go
generated
vendored
Normal file
30
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/json/data_observed.go
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
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,7 +1,7 @@
|
|||
package json
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
|
@ -33,18 +33,6 @@ const (
|
|||
reportDecode
|
||||
)
|
||||
|
||||
// TraceName implements Observable.TraceName
|
||||
func (o observed) TraceName() string {
|
||||
switch o {
|
||||
case reportEncode:
|
||||
return "datacodec/json/encode"
|
||||
case reportDecode:
|
||||
return "datacodec/json/decode"
|
||||
default:
|
||||
return "datacodec/json/unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package datacodec
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
|
@ -33,18 +33,6 @@ const (
|
|||
reportDecode
|
||||
)
|
||||
|
||||
// TraceName implements Observable.TraceName
|
||||
func (o observed) TraceName() string {
|
||||
switch o {
|
||||
case reportEncode:
|
||||
return "datacodec/encode"
|
||||
case reportDecode:
|
||||
return "datacodec/decode"
|
||||
default:
|
||||
return "datacodec/unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
|
|
@ -6,21 +6,12 @@ import (
|
|||
"fmt"
|
||||
)
|
||||
|
||||
func Decode(_ context.Context, in, out interface{}) error {
|
||||
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)
|
||||
}
|
||||
switch s := in.(type) {
|
||||
case string:
|
||||
*p = s
|
||||
case []byte:
|
||||
*p = string(s)
|
||||
case nil: // treat nil like []byte{}
|
||||
*p = ""
|
||||
default:
|
||||
return fmt.Errorf("text.Decode in: want []byte or string, got %T", in)
|
||||
}
|
||||
*p = string(in)
|
||||
return nil
|
||||
}
|
||||
|
||||
30
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/text/data_observed.go
generated
vendored
Normal file
30
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/text/data_observed.go
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
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
|
||||
}
|
||||
4
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/text/doc.go
generated
vendored
Normal file
4
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/text/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Package text holds the encoder/decoder implementation for `text/plain`.
|
||||
*/
|
||||
package text
|
||||
51
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/text/observability.go
generated
vendored
Normal file
51
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/text/observability.go
generated
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
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
|
||||
}
|
||||
35
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/xml/data.go
generated
vendored
Normal file
35
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/xml/data.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
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)
|
||||
}
|
||||
30
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/xml/data_observed.go
generated
vendored
Normal file
30
vendor/github.com/cloudevents/sdk-go/v2/event/datacodec/xml/data_observed.go
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
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,7 +1,7 @@
|
|||
package xml
|
||||
|
||||
import (
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
|
@ -33,18 +33,6 @@ const (
|
|||
reportDecode
|
||||
)
|
||||
|
||||
// TraceName implements Observable.TraceName
|
||||
func (o observed) TraceName() string {
|
||||
switch o {
|
||||
case reportEncode:
|
||||
return "datacodec/xml/encode"
|
||||
case reportDecode:
|
||||
return "datacodec/xml/decode"
|
||||
default:
|
||||
return "datacodec/xml/unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
/*
|
||||
Package cloudevents provides primitives to work with CloudEvents specification: https://github.com/cloudevents/spec.
|
||||
*/
|
||||
package cloudevents
|
||||
package event
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
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, 0)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
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,4 +1,4 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
|
@ -33,19 +33,35 @@ type EventReader interface {
|
|||
// Extensions use the CloudEvents type system, details in package cloudevents/types.
|
||||
Extensions() map[string]interface{}
|
||||
|
||||
// DEPRECATED: see event.Context.ExtensionAs
|
||||
// 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.
|
||||
// data should be a pointer type.
|
||||
DataAs(interface{}) error
|
||||
}
|
||||
|
||||
// EventWriter is the interface for writing through an event onto attributes.
|
||||
// If an error is thrown by a sub-component, EventWriter panics.
|
||||
// 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
|
||||
|
||||
|
|
@ -73,6 +89,9 @@ type EventWriter interface {
|
|||
// SetExtension performs event.Context.SetExtension.
|
||||
SetExtension(string, interface{})
|
||||
|
||||
// SetData encodes the given payload with the current encoding settings.
|
||||
SetData(interface{}) error
|
||||
// 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,15 +1,15 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
errors2 "github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
)
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
|
|
@ -26,7 +26,7 @@ func (e Event) MarshalJSON() ([]byte, error) {
|
|||
var err error
|
||||
|
||||
switch e.SpecVersion() {
|
||||
case CloudEventsVersionV01, CloudEventsVersionV02, CloudEventsVersionV03:
|
||||
case CloudEventsVersionV03:
|
||||
b, err = JsonEncodeLegacy(e)
|
||||
case CloudEventsVersionV1:
|
||||
b, err = JsonEncode(e)
|
||||
|
|
@ -59,10 +59,6 @@ func (e *Event) UnmarshalJSON(b []byte) error {
|
|||
|
||||
var err error
|
||||
switch version {
|
||||
case CloudEventsVersionV01:
|
||||
err = e.JsonDecodeV01(b, raw)
|
||||
case CloudEventsVersionV02:
|
||||
err = e.JsonDecodeV02(b, raw)
|
||||
case CloudEventsVersionV03:
|
||||
err = e.JsonDecodeV03(b, raw)
|
||||
case CloudEventsVersionV1:
|
||||
|
|
@ -82,15 +78,6 @@ func (e *Event) UnmarshalJSON(b []byte) error {
|
|||
}
|
||||
|
||||
func versionFromRawMessage(raw map[string]json.RawMessage) string {
|
||||
// v0.1
|
||||
if v, ok := raw["cloudEventsVersion"]; ok {
|
||||
var version string
|
||||
if err := json.Unmarshal(v, &version); err != nil {
|
||||
return ""
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
// v0.2 and after
|
||||
if v, ok := raw["specversion"]; ok {
|
||||
var version string
|
||||
|
|
@ -104,68 +91,51 @@ func versionFromRawMessage(raw map[string]json.RawMessage) string {
|
|||
|
||||
// JsonEncode
|
||||
func JsonEncode(e Event) ([]byte, error) {
|
||||
data, err := e.DataBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jsonEncode(e.Context, data, e.DataBinary)
|
||||
return jsonEncode(e.Context, e.DataEncoded, e.DataBase64)
|
||||
}
|
||||
|
||||
// JsonEncodeLegacy
|
||||
func JsonEncodeLegacy(e Event) ([]byte, error) {
|
||||
var data []byte
|
||||
isBase64 := e.Context.DeprecatedGetDataContentEncoding() == Base64
|
||||
var err error
|
||||
data, err = e.DataBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jsonEncode(e.Context, data, isBase64)
|
||||
return jsonEncode(e.Context, e.DataEncoded, isBase64)
|
||||
}
|
||||
|
||||
func jsonEncode(ctx EventContextReader, data []byte, isBase64 bool) ([]byte, error) {
|
||||
func jsonEncode(ctx EventContextReader, data []byte, shouldEncodeToBase64 bool) ([]byte, error) {
|
||||
var b map[string]json.RawMessage
|
||||
var err error
|
||||
|
||||
if ctx.GetSpecVersion() == CloudEventsVersionV01 {
|
||||
b, err = marshalEventLegacy(ctx)
|
||||
} else {
|
||||
b, err = marshalEvent(ctx, ctx.GetExtensions())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
// data is passed in as an encoded []byte. That slice might be any
|
||||
// number of things but for json encoding of the envelope all we care
|
||||
// is if the payload is either a string or a json object. If it is a
|
||||
// json object, it can be inserted into the body without modification.
|
||||
// Otherwise we need to quote it if not already quoted.
|
||||
// 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
|
||||
// TODO(#60): we do not support json values at the moment, only objects and lists.
|
||||
if isJson && !isBase64 {
|
||||
// If isJson and no encoding to base64, we don't need to perform additional steps
|
||||
if isJson && !shouldEncodeToBase64 {
|
||||
b["data"] = data
|
||||
} else {
|
||||
var dataKey string
|
||||
if ctx.GetSpecVersion() == CloudEventsVersionV1 {
|
||||
var dataKey = "data"
|
||||
if ctx.GetSpecVersion() == CloudEventsVersionV1 && shouldEncodeToBase64 {
|
||||
dataKey = "data_base64"
|
||||
buf := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
|
||||
base64.StdEncoding.Encode(buf, data)
|
||||
data = buf
|
||||
} else {
|
||||
dataKey = "data"
|
||||
}
|
||||
if data[0] != byte('"') {
|
||||
b[dataKey] = []byte(strconv.QuoteToASCII(string(data)))
|
||||
var dataPointer []byte
|
||||
if shouldEncodeToBase64 {
|
||||
dataPointer, err = json.Marshal(data)
|
||||
} else {
|
||||
// already quoted
|
||||
b[dataKey] = data
|
||||
dataPointer, err = json.Marshal(string(data))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b[dataKey] = dataPointer
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,65 +147,6 @@ func jsonEncode(ctx EventContextReader, data []byte, isBase64 bool) ([]byte, err
|
|||
return body, nil
|
||||
}
|
||||
|
||||
// JsonDecodeV01 takes in the byte representation of a version 0.1 structured json CloudEvent and returns a
|
||||
// cloudevent.Event or an error if there are parsing errors.
|
||||
func (e *Event) JsonDecodeV01(body []byte, raw map[string]json.RawMessage) error {
|
||||
ec := EventContextV01{}
|
||||
if err := json.Unmarshal(body, &ec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
if d, ok := raw["data"]; ok {
|
||||
data = []byte(d)
|
||||
}
|
||||
|
||||
e.Context = &ec
|
||||
e.Data = data
|
||||
e.DataEncoded = data != nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// JsonDecodeV02 takes in the byte representation of a version 0.2 structured json CloudEvent and returns a
|
||||
// cloudevent.Event or an error if there are parsing errors.
|
||||
func (e *Event) JsonDecodeV02(body []byte, raw map[string]json.RawMessage) error {
|
||||
ec := EventContextV02{}
|
||||
if err := json.Unmarshal(body, &ec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: could use reflection to get these.
|
||||
delete(raw, "specversion")
|
||||
delete(raw, "type")
|
||||
delete(raw, "source")
|
||||
delete(raw, "id")
|
||||
delete(raw, "time")
|
||||
delete(raw, "schemaurl")
|
||||
delete(raw, "contenttype")
|
||||
|
||||
var data interface{}
|
||||
if d, ok := raw["data"]; ok {
|
||||
data = []byte(d)
|
||||
}
|
||||
delete(raw, "data")
|
||||
|
||||
if len(raw) > 0 {
|
||||
extensions := make(map[string]interface{}, len(raw))
|
||||
for k, v := range raw {
|
||||
k = strings.ToLower(k)
|
||||
extensions[k] = v
|
||||
}
|
||||
ec.Extensions = extensions
|
||||
}
|
||||
|
||||
e.Context = &ec
|
||||
e.Data = data
|
||||
e.DataEncoded = data != nil
|
||||
|
||||
return 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 {
|
||||
|
|
@ -244,7 +155,6 @@ func (e *Event) JsonDecodeV03(body []byte, raw map[string]json.RawMessage) error
|
|||
return err
|
||||
}
|
||||
|
||||
// TODO: could use reflection to get these.
|
||||
delete(raw, "specversion")
|
||||
delete(raw, "type")
|
||||
delete(raw, "source")
|
||||
|
|
@ -255,24 +165,53 @@ func (e *Event) JsonDecodeV03(body []byte, raw map[string]json.RawMessage) error
|
|||
delete(raw, "datacontenttype")
|
||||
delete(raw, "datacontentencoding")
|
||||
|
||||
var data interface{}
|
||||
var data []byte
|
||||
if d, ok := raw["data"]; ok {
|
||||
data = []byte(d)
|
||||
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)
|
||||
extensions[k] = v
|
||||
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)
|
||||
}
|
||||
}
|
||||
ec.Extensions = extensions
|
||||
}
|
||||
|
||||
e.Context = &ec
|
||||
e.Data = data
|
||||
e.DataEncoded = data != nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -294,9 +233,21 @@ func (e *Event) JsonDecodeV1(body []byte, raw map[string]json.RawMessage) error
|
|||
delete(raw, "dataschema")
|
||||
delete(raw, "datacontenttype")
|
||||
|
||||
var data interface{}
|
||||
var data []byte
|
||||
if d, ok := raw["data"]; ok {
|
||||
data = []byte(d)
|
||||
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")
|
||||
|
||||
|
|
@ -307,38 +258,43 @@ func (e *Event) JsonDecodeV1(body []byte, raw map[string]json.RawMessage) error
|
|||
return err
|
||||
}
|
||||
dataBase64 = tmp
|
||||
|
||||
}
|
||||
delete(raw, "data_base64")
|
||||
|
||||
if len(raw) > 0 {
|
||||
extensions := make(map[string]interface{}, len(raw))
|
||||
for k, v := range raw {
|
||||
k = strings.ToLower(k)
|
||||
var tmp string
|
||||
if err := json.Unmarshal(v, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
extensions[k] = tmp
|
||||
}
|
||||
ec.Extensions = extensions
|
||||
}
|
||||
|
||||
e.Context = &ec
|
||||
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.Data = data
|
||||
e.DataEncoded = data
|
||||
e.DataBase64 = false
|
||||
} else if dataBase64 != nil {
|
||||
e.Data = dataBase64
|
||||
e.DataEncoded = dataBase64
|
||||
e.DataBase64 = true
|
||||
}
|
||||
e.DataEncoded = data != nil
|
||||
|
||||
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 marshalEventLegacy(event interface{}) (map[string]json.RawMessage, error) {
|
||||
b, err := json.Marshal(event)
|
||||
func marshalEvent(eventCtx EventContextReader, extensions map[string]interface{}) (map[string]json.RawMessage, error) {
|
||||
b, err := json.Marshal(eventCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -348,19 +304,12 @@ func marshalEventLegacy(event interface{}) (map[string]json.RawMessage, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return brm, nil
|
||||
}
|
||||
|
||||
func marshalEvent(event interface{}, extensions map[string]interface{}) (map[string]json.RawMessage, error) {
|
||||
b, err := json.Marshal(event)
|
||||
sv, err := json.Marshal(eventCtx.GetSpecVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
brm := map[string]json.RawMessage{}
|
||||
if err := json.Unmarshal(b, &brm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
brm["specversion"] = sv
|
||||
|
||||
for k, v := range extensions {
|
||||
k = strings.ToLower(k)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
"github.com/cloudevents/sdk-go/v2/observability"
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/view"
|
||||
)
|
||||
|
|
@ -38,18 +38,6 @@ const (
|
|||
reportUnmarshal
|
||||
)
|
||||
|
||||
// TraceName implements Observable.TraceName
|
||||
func (o observed) TraceName() string {
|
||||
switch o {
|
||||
case reportMarshal:
|
||||
return "cloudevents/event/marshaljson"
|
||||
case reportUnmarshal:
|
||||
return "cloudevents/event/unmarshaljson"
|
||||
default:
|
||||
return "cloudevents/event/unknwown"
|
||||
}
|
||||
}
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (o observed) MethodName() string {
|
||||
switch o {
|
||||
|
|
@ -78,11 +66,6 @@ type eventJSONObserved struct {
|
|||
// Adheres to Observable
|
||||
var _ observability.Observable = (*eventJSONObserved)(nil)
|
||||
|
||||
// TraceName implements Observable.TraceName
|
||||
func (c eventJSONObserved) TraceName() string {
|
||||
return fmt.Sprintf("%s/%s", c.o.TraceName(), c.v)
|
||||
}
|
||||
|
||||
// MethodName implements Observable.MethodName
|
||||
func (c eventJSONObserved) MethodName() string {
|
||||
return fmt.Sprintf("%s/%s", c.o.MethodName(), c.v)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -9,86 +9,105 @@ var _ EventWriter = (*Event)(nil)
|
|||
|
||||
// SetSpecVersion implements EventWriter.SetSpecVersion
|
||||
func (e *Event) SetSpecVersion(v string) {
|
||||
if e.Context == nil {
|
||||
switch v {
|
||||
case CloudEventsVersionV01:
|
||||
e.Context = EventContextV01{}.AsV01()
|
||||
case CloudEventsVersionV02:
|
||||
e.Context = EventContextV02{}.AsV02()
|
||||
case CloudEventsVersionV03:
|
||||
e.Context = EventContextV03{}.AsV03()
|
||||
case CloudEventsVersionV1:
|
||||
e.Context = EventContextV1{}.AsV1()
|
||||
default:
|
||||
panic(fmt.Errorf("a valid spec version is required: [%s, %s, %s, %s]",
|
||||
CloudEventsVersionV01, CloudEventsVersionV02, CloudEventsVersionV03, CloudEventsVersionV1))
|
||||
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
|
||||
}
|
||||
if err := e.Context.SetSpecVersion(v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e.fieldOK("specversion")
|
||||
return
|
||||
}
|
||||
|
||||
// SetType implements EventWriter.SetType
|
||||
func (e *Event) SetType(t string) {
|
||||
if err := e.Context.SetType(t); err != nil {
|
||||
panic(err)
|
||||
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 {
|
||||
panic(err)
|
||||
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 {
|
||||
panic(err)
|
||||
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 {
|
||||
panic(err)
|
||||
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 {
|
||||
panic(err)
|
||||
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 {
|
||||
panic(err)
|
||||
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 {
|
||||
panic(err)
|
||||
e.fieldError("datacontenttype", err)
|
||||
} else {
|
||||
e.fieldOK("datacontenttype")
|
||||
}
|
||||
}
|
||||
|
||||
// DeprecatedSetDataContentEncoding implements EventWriter.DeprecatedSetDataContentEncoding
|
||||
func (e *Event) SetDataContentEncoding(enc string) {
|
||||
if err := e.Context.DeprecatedSetDataContentEncoding(enc); err != nil {
|
||||
panic(err)
|
||||
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 {
|
||||
panic(err)
|
||||
e.fieldError("extension:"+name, err)
|
||||
} else {
|
||||
e.fieldOK("extension:" + name)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import "time"
|
||||
|
||||
|
|
@ -57,8 +57,6 @@ type EventContextReader interface {
|
|||
// EventContextWriter are the methods required to be a writer of context
|
||||
// attributes.
|
||||
type EventContextWriter interface {
|
||||
// SetSpecVersion sets the spec version of the context.
|
||||
SetSpecVersion(string) error
|
||||
// SetType sets the type of the context.
|
||||
SetType(string) error
|
||||
// SetSource sets the source of the context.
|
||||
|
|
@ -79,6 +77,8 @@ type EventContextWriter interface {
|
|||
// 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
|
||||
}
|
||||
|
|
@ -86,16 +86,6 @@ type EventContextWriter interface {
|
|||
// EventContextConverter are the methods that allow for event version
|
||||
// conversion.
|
||||
type EventContextConverter interface {
|
||||
// AsV01 provides a translation from whatever the "native" encoding of the
|
||||
// CloudEvent was to the equivalent in v0.1 field names, moving fields to or
|
||||
// from extensions as necessary.
|
||||
AsV01() *EventContextV01
|
||||
|
||||
// AsV02 provides a translation from whatever the "native" encoding of the
|
||||
// CloudEvent was to the equivalent in v0.2 field names, moving fields to or
|
||||
// from extensions as necessary.
|
||||
AsV02() *EventContextV02
|
||||
|
||||
// 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.
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -17,12 +18,10 @@ const (
|
|||
// EventContextV03 represents the non-data attributes of a CloudEvents v0.3
|
||||
// event.
|
||||
type EventContextV03 struct {
|
||||
// SpecVersion - The version of the CloudEvents specification used by the event.
|
||||
SpecVersion string `json:"specversion"`
|
||||
// Type - The type of the occurrence which has happened.
|
||||
Type string `json:"type"`
|
||||
// Source - A URI describing the event producer.
|
||||
Source types.URLRef `json:"source"`
|
||||
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"`
|
||||
|
|
@ -31,9 +30,8 @@ type EventContextV03 struct {
|
|||
// 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.URLRef `json:"schemaurl,omitempty"`
|
||||
SchemaURL *types.URIRef `json:"schemaurl,omitempty"`
|
||||
// GetDataMediaType - A MIME (RFC2046) string describing the media type of `data`.
|
||||
// TODO: Should an empty string assume `application/json`, `application/octet-stream`, or auto-detect the content?
|
||||
DataContentType *string `json:"datacontenttype,omitempty"`
|
||||
// DeprecatedDataContentEncoding describes the content encoding for the `data` attribute. Valid: nil, `Base64`.
|
||||
DataContentEncoding *string `json:"datacontentencoding,omitempty"`
|
||||
|
|
@ -82,6 +80,10 @@ func (ec *EventContextV03) SetExtension(name string, value interface{}) error {
|
|||
}
|
||||
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 {
|
||||
|
|
@ -89,61 +91,42 @@ func (ec *EventContextV03) SetExtension(name string, value interface{}) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone implements EventContextConverter.Clone
|
||||
func (ec EventContextV03) Clone() EventContext {
|
||||
return ec.AsV03()
|
||||
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
|
||||
}
|
||||
|
||||
// AsV01 implements EventContextConverter.AsV01
|
||||
func (ec EventContextV03) AsV01() *EventContextV01 {
|
||||
ecv2 := ec.AsV02()
|
||||
return ecv2.AsV01()
|
||||
func (ec *EventContextV03) cloneExtensions() map[string]interface{} {
|
||||
old := ec.Extensions
|
||||
if old == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AsV02 implements EventContextConverter.AsV02
|
||||
func (ec EventContextV03) AsV02() *EventContextV02 {
|
||||
ret := EventContextV02{
|
||||
SpecVersion: CloudEventsVersionV02,
|
||||
ID: ec.ID,
|
||||
Time: ec.Time,
|
||||
Type: ec.Type,
|
||||
SchemaURL: ec.SchemaURL,
|
||||
ContentType: ec.DataContentType,
|
||||
Source: ec.Source,
|
||||
Extensions: make(map[string]interface{}),
|
||||
new := make(map[string]interface{}, len(ec.Extensions))
|
||||
for k, v := range old {
|
||||
new[k] = types.Clone(v)
|
||||
}
|
||||
// Subject was introduced in 0.3, so put it in an extension for 0.2.
|
||||
if ec.Subject != nil {
|
||||
_ = ret.SetExtension(SubjectKey, *ec.Subject)
|
||||
}
|
||||
// DeprecatedDataContentEncoding was introduced in 0.3, so put it in an extension for 0.2.
|
||||
if ec.DataContentEncoding != nil {
|
||||
_ = ret.SetExtension(DataContentEncodingKey, *ec.DataContentEncoding)
|
||||
}
|
||||
if ec.Extensions != nil {
|
||||
for k, v := range ec.Extensions {
|
||||
ret.Extensions[k] = v
|
||||
}
|
||||
}
|
||||
if len(ret.Extensions) == 0 {
|
||||
ret.Extensions = nil
|
||||
}
|
||||
return &ret
|
||||
return new
|
||||
}
|
||||
|
||||
// AsV03 implements EventContextConverter.AsV03
|
||||
func (ec EventContextV03) AsV03() *EventContextV03 {
|
||||
ec.SpecVersion = CloudEventsVersionV03
|
||||
return &ec
|
||||
}
|
||||
|
||||
// AsV04 implements EventContextConverter.AsV04
|
||||
func (ec EventContextV03) AsV1() *EventContextV1 {
|
||||
ret := EventContextV1{
|
||||
SpecVersion: CloudEventsVersionV1,
|
||||
ID: ec.ID,
|
||||
Time: ec.Time,
|
||||
Type: ec.Type,
|
||||
|
|
@ -192,16 +175,6 @@ func (ec EventContextV03) Validate() error {
|
|||
errors = append(errors, "type: MUST be a non-empty string")
|
||||
}
|
||||
|
||||
// specversion
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
specVersion := strings.TrimSpace(ec.SpecVersion)
|
||||
if specVersion == "" {
|
||||
errors = append(errors, "specversion: MUST be a non-empty string")
|
||||
}
|
||||
|
||||
// source
|
||||
// Type: URI-reference
|
||||
// Constraints:
|
||||
|
|
@ -264,8 +237,12 @@ func (ec EventContextV03) Validate() error {
|
|||
if ec.DataContentType != nil {
|
||||
dataContentType := strings.TrimSpace(*ec.DataContentType)
|
||||
if dataContentType == "" {
|
||||
// TODO: need to test for RFC 2046
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +255,6 @@ func (ec EventContextV03) Validate() error {
|
|||
if ec.DataContentEncoding != nil {
|
||||
dataContentEncoding := strings.ToLower(strings.TrimSpace(*ec.DataContentEncoding))
|
||||
if dataContentEncoding != Base64 {
|
||||
// TODO: need to test for RFC 2046
|
||||
errors = append(errors, "datacontentencoding: if present, MUST adhere to RFC 2045 Section 6.1")
|
||||
}
|
||||
}
|
||||
|
|
@ -295,7 +271,7 @@ func (ec EventContextV03) String() string {
|
|||
|
||||
b.WriteString("Context Attributes,\n")
|
||||
|
||||
b.WriteString(" specversion: " + ec.SpecVersion + "\n")
|
||||
b.WriteString(" specversion: " + CloudEventsVersionV03 + "\n")
|
||||
b.WriteString(" type: " + ec.Type + "\n")
|
||||
b.WriteString(" source: " + ec.Source.String() + "\n")
|
||||
if ec.Subject != nil {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -8,9 +8,6 @@ import (
|
|||
|
||||
// GetSpecVersion implements EventContextReader.GetSpecVersion
|
||||
func (ec EventContextV03) GetSpecVersion() string {
|
||||
if ec.SpecVersion != "" {
|
||||
return ec.SpecVersion
|
||||
}
|
||||
return CloudEventsVersionV03
|
||||
}
|
||||
|
||||
|
|
@ -1,27 +1,17 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// Adhere to EventContextWriter
|
||||
var _ EventContextWriter = (*EventContextV03)(nil)
|
||||
|
||||
// SetSpecVersion implements EventContextWriter.SetSpecVersion
|
||||
func (ec *EventContextV03) SetSpecVersion(v string) error {
|
||||
if v != CloudEventsVersionV03 {
|
||||
return fmt.Errorf("invalid version %q, expecting %q", v, CloudEventsVersionV03)
|
||||
}
|
||||
ec.SpecVersion = CloudEventsVersionV03
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataContentType implements EventContextWriter.SetDataContentType
|
||||
func (ec *EventContextV03) SetDataContentType(ct string) error {
|
||||
ct = strings.TrimSpace(ct)
|
||||
|
|
@ -46,7 +36,7 @@ func (ec *EventContextV03) SetSource(u string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.Source = types.URLRef{URL: *pu}
|
||||
ec.Source = types.URIRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +82,7 @@ func (ec *EventContextV03) SetDataSchema(u string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.SchemaURL = &types.URLRef{URL: *pu}
|
||||
ec.SchemaURL = &types.URIRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package cloudevents
|
||||
package event
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
"github.com/cloudevents/sdk-go/v2/types"
|
||||
)
|
||||
|
||||
// WIP: AS OF SEP 20, 2019
|
||||
|
|
@ -26,9 +26,6 @@ type EventContextV1 struct {
|
|||
// Source - A URI describing the event producer.
|
||||
// +required
|
||||
Source types.URIRef `json:"source"`
|
||||
// SpecVersion - The version of the CloudEvents specification used by the event.
|
||||
// +required
|
||||
SpecVersion string `json:"specversion"`
|
||||
// Type - The type of the occurrence which has happened.
|
||||
// +required
|
||||
Type string `json:"type"`
|
||||
|
|
@ -73,8 +70,9 @@ func (ec EventContextV1) ExtensionAs(name string, obj interface{}) error {
|
|||
}
|
||||
|
||||
// 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 !IsAlphaNumericLowercaseLetters(name) {
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +82,9 @@ func (ec *EventContextV1) SetExtension(name string, value interface{}) error {
|
|||
}
|
||||
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
|
||||
|
|
@ -96,39 +97,46 @@ func (ec *EventContextV1) SetExtension(name string, value interface{}) error {
|
|||
|
||||
// Clone implements EventContextConverter.Clone
|
||||
func (ec EventContextV1) Clone() EventContext {
|
||||
return ec.AsV1()
|
||||
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
|
||||
}
|
||||
|
||||
// AsV01 implements EventContextConverter.AsV01
|
||||
func (ec EventContextV1) AsV01() *EventContextV01 {
|
||||
ecv2 := ec.AsV02()
|
||||
return ecv2.AsV01()
|
||||
func (ec *EventContextV1) cloneExtensions() map[string]interface{} {
|
||||
old := ec.Extensions
|
||||
if old == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AsV02 implements EventContextConverter.AsV02
|
||||
func (ec EventContextV1) AsV02() *EventContextV02 {
|
||||
ecv3 := ec.AsV03()
|
||||
return ecv3.AsV02()
|
||||
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{
|
||||
SpecVersion: CloudEventsVersionV03,
|
||||
ID: ec.ID,
|
||||
Time: ec.Time,
|
||||
Type: ec.Type,
|
||||
DataContentType: ec.DataContentType,
|
||||
Source: types.URLRef{URL: ec.Source.URL},
|
||||
Source: types.URIRef{URL: ec.Source.URL},
|
||||
Subject: ec.Subject,
|
||||
Extensions: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if ec.DataSchema != nil {
|
||||
ret.SchemaURL = &types.URLRef{URL: ec.DataSchema.URL}
|
||||
ret.SchemaURL = &types.URIRef{URL: ec.DataSchema.URL}
|
||||
}
|
||||
|
||||
// TODO: DeprecatedDataContentEncoding needs to be moved to extensions.
|
||||
if ec.Extensions != nil {
|
||||
for k, v := range ec.Extensions {
|
||||
k = strings.ToLower(k)
|
||||
|
|
@ -151,7 +159,6 @@ func (ec EventContextV1) AsV03() *EventContextV03 {
|
|||
|
||||
// AsV04 implements EventContextConverter.AsV04
|
||||
func (ec EventContextV1) AsV1() *EventContextV1 {
|
||||
ec.SpecVersion = CloudEventsVersionV1
|
||||
return &ec
|
||||
}
|
||||
|
||||
|
|
@ -183,16 +190,6 @@ func (ec EventContextV1) Validate() error {
|
|||
errors = append(errors, "source: REQUIRED")
|
||||
}
|
||||
|
||||
// specversion
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
specVersion := strings.TrimSpace(ec.SpecVersion)
|
||||
if specVersion == "" {
|
||||
errors = append(errors, "specversion: MUST be a non-empty string")
|
||||
}
|
||||
|
||||
// type
|
||||
// Type: String
|
||||
// Constraints:
|
||||
|
|
@ -218,7 +215,7 @@ func (ec EventContextV1) Validate() error {
|
|||
} else {
|
||||
_, _, err := mime.ParseMediaType(dataContentType)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("datacontenttype: failed to parse media type, %s", err.Error()))
|
||||
errors = append(errors, fmt.Sprintf("datacontenttype: failed to parse RFC 2046 media type, %s", err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -267,7 +264,7 @@ func (ec EventContextV1) String() string {
|
|||
|
||||
b.WriteString("Context Attributes,\n")
|
||||
|
||||
b.WriteString(" specversion: " + ec.SpecVersion + "\n")
|
||||
b.WriteString(" specversion: " + CloudEventsVersionV1 + "\n")
|
||||
b.WriteString(" type: " + ec.Type + "\n")
|
||||
b.WriteString(" source: " + ec.Source.String() + "\n")
|
||||
if ec.Subject != nil {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue