mirror of https://github.com/knative/docs.git
This adds a simple cloudevents-go sample. (#1987)
This example is intended to showcase how to start an event receiver as a Knative Service, and then have that service either send a new event to a `$K_SINK` or respond with it (e.g. for working with a Broker). Once we're happy with this, we should broaden this to cover more languages.
This commit is contained in:
parent
9902cb7140
commit
e07e9bab0d
|
@ -26,7 +26,7 @@
|
|||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d3c3de7c1ad57f795e5c409afa842fea27899f124622bcdce83f8bea8721b9db"
|
||||
digest = "1:902544577dcb868a5ae31529d73a1ce5031e224923290caedf6176241ee304e0"
|
||||
name = "github.com/cloudevents/sdk-go"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -35,6 +35,7 @@
|
|||
"pkg/cloudevents/context",
|
||||
"pkg/cloudevents/datacodec",
|
||||
"pkg/cloudevents/datacodec/json",
|
||||
"pkg/cloudevents/datacodec/text",
|
||||
"pkg/cloudevents/datacodec/xml",
|
||||
"pkg/cloudevents/observability",
|
||||
"pkg/cloudevents/transport",
|
||||
|
@ -42,8 +43,8 @@
|
|||
"pkg/cloudevents/types",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "4cc108a637ff4bf2d1848c60d5fbb0f711fd1b8c"
|
||||
version = "v0.9.2"
|
||||
revision = "2fa4bb1fbb4aac4d906b0173a2a408f701439b82"
|
||||
version = "v0.10.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7a6852b35eb5bbc184561443762d225116ae630c26a7c4d90546619f1e7d2ad2"
|
||||
|
@ -142,6 +143,14 @@
|
|||
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
|
||||
version = "v1.6.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:08c58ac78a8c1f61e9a96350066d30fe194b8779799bd932a79932a5166a173f"
|
||||
name = "github.com/kelseyhightower/envconfig"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "0b417c4ec4a8a82eecc22a1459a504aa55163d61"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6"
|
||||
name = "github.com/matttproud/golang_protobuf_extensions"
|
||||
|
@ -477,6 +486,7 @@
|
|||
"github.com/google/go-github/github",
|
||||
"github.com/google/uuid",
|
||||
"github.com/gorilla/mux",
|
||||
"github.com/kelseyhightower/envconfig",
|
||||
"github.com/openzipkin/zipkin-go",
|
||||
"github.com/openzipkin/zipkin-go/reporter/http",
|
||||
"github.com/satori/go.uuid",
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: "Knative Serving 'Cloud Events' samples"
|
||||
linkTitle: "Cloud Events apps"
|
||||
weight: 1
|
||||
type: "docs"
|
||||
---
|
|
@ -0,0 +1,31 @@
|
|||
# Use the official Golang image to create a build artifact.
|
||||
# This is based on Debian and sets the GOPATH to /go.
|
||||
# https://hub.docker.com/_/golang
|
||||
FROM golang:1.13 as builder
|
||||
|
||||
# Create and change to the app directory.
|
||||
WORKDIR /app
|
||||
|
||||
# Retrieve application dependencies using go modules.
|
||||
# Allows container builds to reuse downloaded dependencies.
|
||||
COPY go.* ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy local code to the container image.
|
||||
COPY . ./
|
||||
|
||||
# Build the binary.
|
||||
# -mod=readonly ensures immutable go.mod and go.sum in container builds.
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -mod=readonly -v -o server
|
||||
|
||||
# Use the official Alpine image for a lean production container.
|
||||
# https://hub.docker.com/_/alpine
|
||||
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
|
||||
FROM alpine:3
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
# Copy the binary to the production image from the builder stage.
|
||||
COPY --from=builder /app/server /server
|
||||
|
||||
# Run the web service on container startup.
|
||||
CMD ["/server"]
|
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
title: "Cloud Events - Go"
|
||||
linkTitle: "Go"
|
||||
weight: 1
|
||||
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.
|
||||
implementing an event source)
|
||||
|
||||
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
|
||||
following commands:
|
||||
|
||||
```shell
|
||||
git clone -b "{{< branch >}}" https://github.com/knative/docs knative-docs
|
||||
cd knative-docs/docs/serving/samples/cloudevents/cloudevents-go
|
||||
```
|
||||
|
||||
## Before you begin
|
||||
|
||||
- A Kubernetes cluster with Knative installed and DNS configured. Follow the
|
||||
[installation instructions](../../../../install/README.md) if you need to
|
||||
create one.
|
||||
- [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).
|
||||
|
||||
## The sample code.
|
||||
|
||||
1. If you look in `cloudevents.go`, you will see two key functions for the
|
||||
different modes of operation:
|
||||
|
||||
```go
|
||||
func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Event) error {
|
||||
// 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 {
|
||||
// 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:
|
||||
|
||||
```shell
|
||||
docker build -t <image> .
|
||||
docker push <image>
|
||||
```
|
||||
|
||||
Alternatively you can use [`ko`](https://github.com/google/ko) to build and
|
||||
push just the image with:
|
||||
|
||||
```shell
|
||||
ko publish github.com/knative/docs/docs/serving/samples/cloudevents/cloudevents-go
|
||||
```
|
||||
|
||||
1. If you look in `service.yaml`, take the `<image>` name above and insert it
|
||||
into the `image:` field (unless using `ko`).
|
||||
|
||||
```shell
|
||||
kubectl apply -f service.yaml
|
||||
```
|
||||
|
||||
Or if using `ko` to build and push:
|
||||
|
||||
```shell
|
||||
ko apply -f service.yaml
|
||||
```
|
||||
|
||||
## Testing the sample
|
||||
|
||||
Get the URL for your Service with:
|
||||
|
||||
```shell
|
||||
$ kubectl get ksvc
|
||||
NAME URL LATESTCREATED LATESTREADY READY REASON
|
||||
cloudevents-go http://cloudevents-go.default.1.2.3.4.xip.io cloudevents-go-ss5pj cloudevents-go-ss5pj True
|
||||
```
|
||||
|
||||
Then send a cloud event to it with:
|
||||
|
||||
```shell
|
||||
$ curl -X POST \
|
||||
-H "content-type: application/json" \
|
||||
-H "ce-specversion: 1.0" \
|
||||
-H "ce-source: curl-command" \
|
||||
-H "ce-type: curl.demo" \
|
||||
-H "ce-id: 123-abc" \
|
||||
-d '{"name":"Dave"}' \
|
||||
http://cloudevents-go.default.1.2.3.4.xip.io
|
||||
```
|
||||
|
||||
You will get back:
|
||||
|
||||
```shell
|
||||
{"message":"Hello, Dave"}
|
||||
```
|
||||
|
||||
|
||||
## Removing the sample app deployment
|
||||
|
||||
To remove the sample app from your cluster, delete the service record:
|
||||
|
||||
```shell
|
||||
kubectl delete --filename service.yaml
|
||||
```
|
|
@ -0,0 +1,105 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
cloudevents "github.com/cloudevents/sdk-go"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
)
|
||||
|
||||
type Receiver struct {
|
||||
client cloudevents.Client
|
||||
|
||||
// If the K_SINK environment variable is set, then events are sent there,
|
||||
// otherwise we simply reply to the inbound request.
|
||||
Target string `envconfig:"K_SINK"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
client, err := cloudevents.NewDefaultClient()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
r := Receiver{client: client}
|
||||
if err := envconfig.Process("", &r); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Depending on whether targetting data has been supplied,
|
||||
// we will either reply with our response or send it on to
|
||||
// an event sink.
|
||||
var receiver interface{} // the SDK reflects on the signature.
|
||||
if r.Target == "" {
|
||||
receiver = r.ReceiveAndReply
|
||||
} else {
|
||||
receiver = r.ReceiveAndSend
|
||||
}
|
||||
|
||||
if err := client.StartReceiver(context.Background(), receiver); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Request is the structure of the event we expect to receive.
|
||||
type Request struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Response is the structure of the event we send in response to requests.
|
||||
type Response struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ReceiveAndSend is invoked whenever we receive an event.
|
||||
func (recv *Receiver) ReceiveAndSend(ctx context.Context, event cloudevents.Event) error {
|
||||
req := Request{}
|
||||
if err := event.DataAs(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Got an event from: %q", req.Name)
|
||||
|
||||
resp := handle(req)
|
||||
log.Printf("Sending event: %q", resp.Message)
|
||||
|
||||
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)
|
||||
|
||||
ctx = cloudevents.ContextWithTarget(ctx, recv.Target)
|
||||
_, _, err := recv.client.Send(ctx, event)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReceiveAndReply is invoked whenever we receive an event.
|
||||
func (recv *Receiver) ReceiveAndReply(ctx context.Context, event cloudevents.Event, eventResp *cloudevents.EventResponse) error {
|
||||
req := Request{}
|
||||
if err := event.DataAs(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Got an event from: %q", req.Name)
|
||||
|
||||
resp := handle(req)
|
||||
log.Printf("Replying with event: %q", resp.Message)
|
||||
|
||||
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
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: serving.knative.dev/v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: cloudevents-go
|
||||
namespace: default
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: github.com/knative/docs/docs/serving/samples/cloudevents/cloudevents-go
|
||||
# Uncomment this to send events somewhere.
|
||||
# env:
|
||||
# - name: K_SINK
|
||||
# value: http://default-broker.default.svc.cluster.local
|
|
@ -26,6 +26,7 @@ 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
|
||||
|
@ -54,12 +55,16 @@ const (
|
|||
|
||||
// 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
|
||||
|
@ -114,6 +119,8 @@ var (
|
|||
|
||||
ParseTimestamp = types.ParseTimestamp
|
||||
ParseURLRef = types.ParseURLRef
|
||||
ParseURIRef = types.ParseURIRef
|
||||
ParseURI = types.ParseURI
|
||||
|
||||
// HTTP Transport
|
||||
|
||||
|
@ -133,6 +140,7 @@ var (
|
|||
WithPath = http.WithPath
|
||||
WithMiddleware = http.WithMiddleware
|
||||
WithLongPollTarget = http.WithLongPollTarget
|
||||
WithListener = http.WithListener
|
||||
|
||||
// HTTP Context
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
@ -30,12 +31,14 @@ func init() {
|
|||
AddDecoder("text/json", json.Decode)
|
||||
AddDecoder("application/xml", xml.Decode)
|
||||
AddDecoder("text/xml", xml.Decode)
|
||||
AddDecoder("text/plain", text.Decode)
|
||||
|
||||
AddEncoder("", json.Encode)
|
||||
AddEncoder("application/json", json.Encode)
|
||||
AddEncoder("text/json", json.Encode)
|
||||
AddEncoder("application/xml", xml.Encode)
|
||||
AddEncoder("text/xml", xml.Encode)
|
||||
AddEncoder("text/plain", text.Encode)
|
||||
}
|
||||
|
||||
// AddDecoder registers a decoder for a given content type. The codecs will use
|
||||
|
|
33
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/text/text.go
generated
vendored
Normal file
33
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/datacodec/text/text.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Text codec converts []byte or string to string and vice-versa.
|
||||
package text
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Decode(_ context.Context, in, 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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Encode(_ context.Context, in interface{}) ([]byte, error) {
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("text.Encode in: want string, got %T", in)
|
||||
}
|
||||
return []byte(s), nil
|
||||
}
|
|
@ -12,6 +12,7 @@ type Event struct {
|
|||
Context EventContext
|
||||
Data interface{}
|
||||
DataEncoded bool
|
||||
DataBinary bool
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -30,7 +31,17 @@ func New(version ...string) Event {
|
|||
return *e
|
||||
}
|
||||
|
||||
// ExtensionAs returns 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"])
|
||||
//
|
||||
func (e Event) ExtensionAs(name string, obj interface{}) error {
|
||||
return e.Context.ExtensionAs(name, obj)
|
||||
}
|
||||
|
|
|
@ -14,11 +14,43 @@ import (
|
|||
|
||||
// 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.DataContentEncoding() == Base64 {
|
||||
if e.DeprecatedDataContentEncoding() == Base64 {
|
||||
buf := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
|
||||
base64.StdEncoding.Encode(buf, data)
|
||||
e.Data = string(buf)
|
||||
|
@ -70,7 +102,7 @@ func (e Event) DataAs(data interface{}) error { // TODO: Clean this function up
|
|||
// No data.
|
||||
return nil
|
||||
}
|
||||
if e.Context.GetDataContentEncoding() == Base64 {
|
||||
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] {
|
||||
|
@ -91,9 +123,13 @@ func (e Event) DataAs(data interface{}) error { // TODO: Clean this function up
|
|||
obj = buf[:n]
|
||||
}
|
||||
|
||||
mediaType, err := e.Context.GetDataMediaType()
|
||||
if err != nil {
|
||||
return err
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -18,26 +18,29 @@ type EventReader interface {
|
|||
ID() string
|
||||
// Time returns event.Context.GetTime().
|
||||
Time() time.Time
|
||||
// SchemaURL returns event.Context.GetSchemaURL().
|
||||
SchemaURL() string
|
||||
// DataSchema returns event.Context.GetDataSchema().
|
||||
DataSchema() string
|
||||
// DataContentType returns event.Context.GetDataContentType().
|
||||
DataContentType() string
|
||||
// DataMediaType returns event.Context.GetDataMediaType().
|
||||
DataMediaType() string
|
||||
// DataContentEncoding returns event.Context.GetDataContentEncoding().
|
||||
DataContentEncoding() string
|
||||
// DeprecatedDataContentEncoding returns event.Context.DeprecatedGetDataContentEncoding().
|
||||
DeprecatedDataContentEncoding() string
|
||||
|
||||
// Extension Attributes
|
||||
|
||||
// Extensions returns the event.Context.GetExtensions().
|
||||
// Extensions use the CloudEvents type system, details in package cloudevents/types.
|
||||
Extensions() map[string]interface{}
|
||||
|
||||
// DEPRECATED: see event.Context.ExtensionAs
|
||||
// ExtensionAs returns event.Context.ExtensionAs(name, obj).
|
||||
ExtensionAs(string, interface{}) error
|
||||
|
||||
// Data Attribute
|
||||
|
||||
// ExtensionAs returns event.Context.ExtensionAs(name, obj).
|
||||
// DataAs attempts to populate the provided data object with the event payload.
|
||||
// data should be a pointer type.
|
||||
DataAs(interface{}) error
|
||||
}
|
||||
|
||||
|
@ -58,11 +61,11 @@ type EventWriter interface {
|
|||
SetID(string)
|
||||
// SetTime performs event.Context.SetTime.
|
||||
SetTime(time.Time)
|
||||
// SetSchemaURL performs event.Context.SetSchemaURL.
|
||||
SetSchemaURL(string)
|
||||
// SetDataSchema performs event.Context.SetDataSchema.
|
||||
SetDataSchema(string)
|
||||
// SetDataContentType performs event.Context.SetDataContentType.
|
||||
SetDataContentType(string)
|
||||
// SetDataContentEncoding performs event.Context.SetDataContentEncoding.
|
||||
// DeprecatedSetDataContentEncoding performs event.Context.DeprecatedSetDataContentEncoding.
|
||||
SetDataContentEncoding(string)
|
||||
|
||||
// Extension Attributes
|
||||
|
|
|
@ -2,9 +2,12 @@ package cloudevents
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/observability"
|
||||
)
|
||||
|
@ -19,7 +22,17 @@ func (e Event) MarshalJSON() ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
b, err := JsonEncode(e)
|
||||
var b []byte
|
||||
var err error
|
||||
|
||||
switch e.SpecVersion() {
|
||||
case CloudEventsVersionV01, CloudEventsVersionV02, CloudEventsVersionV03:
|
||||
b, err = JsonEncodeLegacy(e)
|
||||
case CloudEventsVersionV1:
|
||||
b, err = JsonEncode(e)
|
||||
default:
|
||||
return nil, fmt.Errorf("unnknown spec version: %q", e.SpecVersion())
|
||||
}
|
||||
|
||||
// Report the observable
|
||||
if err != nil {
|
||||
|
@ -52,6 +65,8 @@ func (e *Event) UnmarshalJSON(b []byte) error {
|
|||
err = e.JsonDecodeV02(b, raw)
|
||||
case CloudEventsVersionV03:
|
||||
err = e.JsonDecodeV03(b, raw)
|
||||
case CloudEventsVersionV1:
|
||||
err = e.JsonDecodeV1(b, raw)
|
||||
default:
|
||||
return fmt.Errorf("unnknown spec version: %q", version)
|
||||
}
|
||||
|
@ -89,17 +104,26 @@ func versionFromRawMessage(raw map[string]json.RawMessage) string {
|
|||
|
||||
// JsonEncode
|
||||
func JsonEncode(e Event) ([]byte, error) {
|
||||
if e.DataContentType() == "" {
|
||||
e.SetDataContentType(ApplicationJSON)
|
||||
}
|
||||
data, err := e.DataBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return jsonEncode(e.Context, data)
|
||||
return jsonEncode(e.Context, data, e.DataBinary)
|
||||
}
|
||||
|
||||
func jsonEncode(ctx EventContextReader, data []byte) ([]byte, error) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
func jsonEncode(ctx EventContextReader, data []byte, isBase64 bool) ([]byte, error) {
|
||||
var b map[string]json.RawMessage
|
||||
var err error
|
||||
|
||||
|
@ -122,16 +146,26 @@ func jsonEncode(ctx EventContextReader, data []byte) ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isBase64 := ctx.GetDataContentEncoding() == Base64
|
||||
isJson := mediaType == "" || mediaType == ApplicationJSON || mediaType == TextJSON
|
||||
// TODO(#60): we do not support json values at the moment, only objects and lists.
|
||||
if isJson && !isBase64 {
|
||||
b["data"] = data
|
||||
} else if data[0] != byte('"') {
|
||||
b["data"] = []byte(strconv.QuoteToASCII(string(data)))
|
||||
} else {
|
||||
// already quoted
|
||||
b["data"] = data
|
||||
var dataKey string
|
||||
if ctx.GetSpecVersion() == CloudEventsVersionV1 {
|
||||
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)))
|
||||
} else {
|
||||
// already quoted
|
||||
b[dataKey] = data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,6 +223,7 @@ func (e *Event) JsonDecodeV02(body []byte, raw map[string]json.RawMessage) error
|
|||
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
|
||||
|
@ -229,6 +264,7 @@ func (e *Event) JsonDecodeV03(body []byte, raw map[string]json.RawMessage) error
|
|||
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
|
||||
|
@ -241,6 +277,66 @@ func (e *Event) JsonDecodeV03(body []byte, raw map[string]json.RawMessage) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// JsonDecodeV1 takes in the byte representation of a version 1.0 structured json CloudEvent and returns a
|
||||
// cloudevent.Event or an error if there are parsing errors.
|
||||
func (e *Event) JsonDecodeV1(body []byte, raw map[string]json.RawMessage) error {
|
||||
ec := EventContextV1{}
|
||||
if err := json.Unmarshal(body, &ec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(raw, "specversion")
|
||||
delete(raw, "type")
|
||||
delete(raw, "source")
|
||||
delete(raw, "subject")
|
||||
delete(raw, "id")
|
||||
delete(raw, "time")
|
||||
delete(raw, "dataschema")
|
||||
delete(raw, "datacontenttype")
|
||||
|
||||
var data interface{}
|
||||
if d, ok := raw["data"]; ok {
|
||||
data = []byte(d)
|
||||
}
|
||||
delete(raw, "data")
|
||||
|
||||
var dataBase64 []byte
|
||||
if d, ok := raw["data_base64"]; ok {
|
||||
var tmp []byte
|
||||
if err := json.Unmarshal(d, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
dataBase64 = tmp
|
||||
}
|
||||
delete(raw, "data_base64")
|
||||
|
||||
if 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
|
||||
} else if dataBase64 != nil {
|
||||
e.Data = dataBase64
|
||||
}
|
||||
e.DataEncoded = data != nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalEventLegacy(event interface{}) (map[string]json.RawMessage, error) {
|
||||
b, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
|
@ -267,6 +363,7 @@ func marshalEvent(event interface{}, extensions map[string]interface{}) (map[str
|
|||
}
|
||||
|
||||
for k, v := range extensions {
|
||||
k = strings.ToLower(k)
|
||||
vb, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -54,10 +54,10 @@ func (e Event) Time() time.Time {
|
|||
return time.Time{}
|
||||
}
|
||||
|
||||
// SchemaURL implements EventReader.SchemaURL
|
||||
func (e Event) SchemaURL() string {
|
||||
// DataSchema implements EventReader.DataSchema
|
||||
func (e Event) DataSchema() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetSchemaURL()
|
||||
return e.Context.GetDataSchema()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -81,15 +81,15 @@ func (e Event) DataMediaType() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// DataContentEncoding implements EventReader.DataContentEncoding
|
||||
func (e Event) DataContentEncoding() string {
|
||||
// DeprecatedDataContentEncoding implements EventReader.DeprecatedDataContentEncoding
|
||||
func (e Event) DeprecatedDataContentEncoding() string {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetDataContentEncoding()
|
||||
return e.Context.DeprecatedGetDataContentEncoding()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DataContentEncoding implements EventReader.DataContentEncoding
|
||||
// Extensions implements EventReader.Extensions
|
||||
func (e Event) Extensions() map[string]interface{} {
|
||||
if e.Context != nil {
|
||||
return e.Context.GetExtensions()
|
||||
|
|
|
@ -17,9 +17,11 @@ func (e *Event) SetSpecVersion(v string) {
|
|||
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]",
|
||||
CloudEventsVersionV01, CloudEventsVersionV02, CloudEventsVersionV03))
|
||||
panic(fmt.Errorf("a valid spec version is required: [%s, %s, %s, %s]",
|
||||
CloudEventsVersionV01, CloudEventsVersionV02, CloudEventsVersionV03, CloudEventsVersionV1))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -63,9 +65,9 @@ func (e *Event) SetTime(t time.Time) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetSchemaURL implements EventWriter.SetSchemaURL
|
||||
func (e *Event) SetSchemaURL(s string) {
|
||||
if err := e.Context.SetSchemaURL(s); err != nil {
|
||||
// SetDataSchema implements EventWriter.SetDataSchema
|
||||
func (e *Event) SetDataSchema(s string) {
|
||||
if err := e.Context.SetDataSchema(s); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -77,14 +79,14 @@ func (e *Event) SetDataContentType(ct string) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetDataContentEncoding implements EventWriter.SetDataContentEncoding
|
||||
// DeprecatedSetDataContentEncoding implements EventWriter.DeprecatedSetDataContentEncoding
|
||||
func (e *Event) SetDataContentEncoding(enc string) {
|
||||
if err := e.Context.SetDataContentEncoding(enc); err != nil {
|
||||
if err := e.Context.DeprecatedSetDataContentEncoding(enc); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDataContentEncoding implements EventWriter.SetDataContentEncoding
|
||||
// SetExtension implements EventWriter.SetExtension
|
||||
func (e *Event) SetExtension(name string, obj interface{}) {
|
||||
if err := e.Context.SetExtension(name, obj); err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -18,27 +18,40 @@ type EventContextReader interface {
|
|||
GetID() string
|
||||
// GetTime returns the CloudEvents creation time from the context.
|
||||
GetTime() time.Time
|
||||
// GetSchemaURL returns the CloudEvents schema URL (if any) from the
|
||||
// GetDataSchema returns the CloudEvents schema URL (if any) from the
|
||||
// context.
|
||||
GetSchemaURL() string
|
||||
GetDataSchema() string
|
||||
// GetDataContentType returns content type on the context.
|
||||
GetDataContentType() string
|
||||
// GetDataContentEncoding returns content encoding on the context.
|
||||
GetDataContentEncoding() string
|
||||
// DeprecatedGetDataContentEncoding returns content encoding on the context.
|
||||
DeprecatedGetDataContentEncoding() string
|
||||
|
||||
// GetDataMediaType returns the MIME media type for encoded data, which is
|
||||
// needed by both encoding and decoding. This is a processed form of
|
||||
// GetDataContentType and it may return an error.
|
||||
GetDataMediaType() (string, error)
|
||||
|
||||
// ExtensionAs populates the given interface with the CloudEvents extension
|
||||
// of the given name from the extension attributes. It returns an error if
|
||||
// the extension does not exist, the extension's type does not match the
|
||||
// provided type, or if the type is not a supported.
|
||||
// DEPRECATED: Access extensions directly via the GetExtensions()
|
||||
// For example replace this:
|
||||
//
|
||||
// var i int
|
||||
// err := ec.ExtensionAs("foo", &i)
|
||||
//
|
||||
// With this:
|
||||
//
|
||||
// i, err := types.ToInteger(ec.GetExtensions["foo"])
|
||||
//
|
||||
ExtensionAs(string, interface{}) error
|
||||
|
||||
// GetExtensions returns the full extensions map.
|
||||
//
|
||||
// Extensions use the CloudEvents type system, details in package cloudevents/types.
|
||||
GetExtensions() map[string]interface{}
|
||||
|
||||
// GetExtension returns the extension associated with with the given key.
|
||||
// The given key is case insensitive. If the extension can not be found,
|
||||
// an error will be returned.
|
||||
GetExtension(string) (interface{}, error)
|
||||
}
|
||||
|
||||
// EventContextWriter are the methods required to be a writer of context
|
||||
|
@ -56,18 +69,22 @@ type EventContextWriter interface {
|
|||
SetID(string) error
|
||||
// SetTime sets the time of the context.
|
||||
SetTime(time time.Time) error
|
||||
// SetSchemaURL sets the schema url of the context.
|
||||
SetSchemaURL(string) error
|
||||
// SetDataSchema sets the schema url of the context.
|
||||
SetDataSchema(string) error
|
||||
// SetDataContentType sets the data content type of the context.
|
||||
SetDataContentType(string) error
|
||||
// SetDataContentEncoding sets the data context encoding of the context.
|
||||
SetDataContentEncoding(string) error
|
||||
// DeprecatedSetDataContentEncoding sets the data context encoding of the context.
|
||||
DeprecatedSetDataContentEncoding(string) error
|
||||
|
||||
// SetExtension sets the given interface onto the extension attributes
|
||||
// determined by the provided name.
|
||||
//
|
||||
// Package ./types documents the types that are allowed as extension values.
|
||||
SetExtension(string, interface{}) error
|
||||
}
|
||||
|
||||
// EventContextConverter are the methods that allow for event version
|
||||
// conversion.
|
||||
type EventContextConverter interface {
|
||||
// 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
|
||||
|
@ -83,6 +100,11 @@ type EventContextConverter interface {
|
|||
// CloudEvent was to the equivalent in v0.3 field names, moving fields to or
|
||||
// from extensions as necessary.
|
||||
AsV03() *EventContextV03
|
||||
|
||||
// AsV1 provides a translation from whatever the "native" encoding of the
|
||||
// CloudEvent was to the equivalent in v1.0 field names, moving fields to or
|
||||
// from extensions as necessary.
|
||||
AsV1() *EventContextV1
|
||||
}
|
||||
|
||||
// EventContext is conical interface for a CloudEvents Context.
|
||||
|
|
|
@ -115,8 +115,12 @@ func (ec EventContextV01) AsV02() *EventContextV02 {
|
|||
|
||||
// AsV03 implements EventContextConverter.AsV03
|
||||
func (ec EventContextV01) AsV03() *EventContextV03 {
|
||||
ecv2 := ec.AsV02()
|
||||
return ecv2.AsV03()
|
||||
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.
|
||||
|
|
19
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_reader.go
generated
vendored
19
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_reader.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"time"
|
||||
)
|
||||
|
@ -68,16 +69,16 @@ func (ec EventContextV01) GetTime() time.Time {
|
|||
return time.Time{}
|
||||
}
|
||||
|
||||
// GetSchemaURL implements EventContextReader.GetSchemaURL
|
||||
func (ec EventContextV01) GetSchemaURL() string {
|
||||
// GetDataSchema implements EventContextReader.GetDataSchema
|
||||
func (ec EventContextV01) GetDataSchema() string {
|
||||
if ec.SchemaURL != nil {
|
||||
return ec.SchemaURL.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetDataContentEncoding implements EventContextReader.GetDataContentEncoding
|
||||
func (ec EventContextV01) GetDataContentEncoding() string {
|
||||
// DeprecatedGetDataContentEncoding implements EventContextReader.DeprecatedGetDataContentEncoding
|
||||
func (ec EventContextV01) DeprecatedGetDataContentEncoding() string {
|
||||
var enc string
|
||||
if err := ec.ExtensionAs(DataContentEncodingKey, &enc); err != nil {
|
||||
return ""
|
||||
|
@ -85,6 +86,16 @@ func (ec EventContextV01) GetDataContentEncoding() string {
|
|||
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
|
||||
}
|
||||
|
|
8
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_writer.go
generated
vendored
8
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v01_writer.go
generated
vendored
|
@ -79,8 +79,8 @@ func (ec *EventContextV01) SetTime(t time.Time) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetSchemaURL implements EventContextWriter.SetSchemaURL
|
||||
func (ec *EventContextV01) SetSchemaURL(u string) error {
|
||||
// SetDataSchema implements EventContextWriter.SetDataSchema
|
||||
func (ec *EventContextV01) SetDataSchema(u string) error {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
ec.SchemaURL = nil
|
||||
|
@ -94,8 +94,8 @@ func (ec *EventContextV01) SetSchemaURL(u string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetDataContentEncoding implements EventContextWriter.SetDataContentEncoding
|
||||
func (ec *EventContextV01) SetDataContentEncoding(e string) error {
|
||||
// DeprecatedSetDataContentEncoding implements EventContextWriter.DeprecatedSetDataContentEncoding
|
||||
func (ec *EventContextV01) DeprecatedSetDataContentEncoding(e string) error {
|
||||
e = strings.ToLower(strings.TrimSpace(e))
|
||||
if e == "" {
|
||||
return ec.SetExtension(DataContentEncodingKey, nil)
|
||||
|
|
|
@ -146,7 +146,7 @@ func (ec EventContextV02) AsV03() *EventContextV03 {
|
|||
}
|
||||
continue
|
||||
}
|
||||
// DataContentEncoding was introduced in 0.3
|
||||
// DeprecatedDataContentEncoding was introduced in 0.3
|
||||
if strings.EqualFold(k, DataContentEncodingKey) {
|
||||
etv, ok := v.(string)
|
||||
if ok && etv != "" {
|
||||
|
@ -163,6 +163,11 @@ func (ec EventContextV02) AsV03() *EventContextV03 {
|
|||
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 {
|
||||
|
|
19
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_reader.go
generated
vendored
19
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_reader.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"time"
|
||||
)
|
||||
|
@ -48,8 +49,8 @@ func (ec EventContextV02) GetTime() time.Time {
|
|||
return time.Time{}
|
||||
}
|
||||
|
||||
// GetSchemaURL implements EventContextReader.GetSchemaURL
|
||||
func (ec EventContextV02) GetSchemaURL() string {
|
||||
// GetDataSchema implements EventContextReader.GetDataSchema
|
||||
func (ec EventContextV02) GetDataSchema() string {
|
||||
if ec.SchemaURL != nil {
|
||||
return ec.SchemaURL.String()
|
||||
}
|
||||
|
@ -76,8 +77,8 @@ func (ec EventContextV02) GetDataMediaType() (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// GetDataContentEncoding implements EventContextReader.GetDataContentEncoding
|
||||
func (ec EventContextV02) GetDataContentEncoding() string {
|
||||
// DeprecatedGetDataContentEncoding implements EventContextReader.DeprecatedGetDataContentEncoding
|
||||
func (ec EventContextV02) DeprecatedGetDataContentEncoding() string {
|
||||
var enc string
|
||||
if err := ec.ExtensionAs(DataContentEncodingKey, &enc); err != nil {
|
||||
return ""
|
||||
|
@ -85,6 +86,16 @@ func (ec EventContextV02) GetDataContentEncoding() string {
|
|||
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
|
||||
}
|
||||
|
|
8
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_writer.go
generated
vendored
8
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v02_writer.go
generated
vendored
|
@ -79,8 +79,8 @@ func (ec *EventContextV02) SetTime(t time.Time) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetSchemaURL implements EventContextWriter.SetSchemaURL
|
||||
func (ec *EventContextV02) SetSchemaURL(u string) error {
|
||||
// SetDataSchema implements EventContextWriter.SetDataSchema
|
||||
func (ec *EventContextV02) SetDataSchema(u string) error {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
ec.SchemaURL = nil
|
||||
|
@ -94,8 +94,8 @@ func (ec *EventContextV02) SetSchemaURL(u string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetDataContentEncoding implements EventContextWriter.SetDataContentEncoding
|
||||
func (ec *EventContextV02) SetDataContentEncoding(e string) error {
|
||||
// DeprecatedSetDataContentEncoding implements EventContextWriter.DeprecatedSetDataContentEncoding
|
||||
func (ec *EventContextV02) DeprecatedSetDataContentEncoding(e string) error {
|
||||
e = strings.ToLower(strings.TrimSpace(e))
|
||||
if e == "" {
|
||||
return ec.SetExtension(DataContentEncodingKey, nil)
|
||||
|
|
|
@ -9,8 +9,6 @@ import (
|
|||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// WIP: AS OF FEB 19, 2019
|
||||
|
||||
const (
|
||||
// CloudEventsVersionV03 represents the version 0.3 of the CloudEvents spec.
|
||||
CloudEventsVersionV03 = "0.3"
|
||||
|
@ -32,12 +30,12 @@ type EventContextV03 struct {
|
|||
ID string `json:"id"`
|
||||
// Time - A Timestamp when the event happened.
|
||||
Time *types.Timestamp `json:"time,omitempty"`
|
||||
// SchemaURL - A link to the schema that the `data` attribute adheres to.
|
||||
// DataSchema - A link to the schema that the `data` attribute adheres to.
|
||||
SchemaURL *types.URLRef `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"`
|
||||
// DataContentEncoding describes the content encoding for the `data` attribute. Valid: nil, `Base64`.
|
||||
// DeprecatedDataContentEncoding describes the content encoding for the `data` attribute. Valid: nil, `Base64`.
|
||||
DataContentEncoding *string `json:"datacontentencoding,omitempty"`
|
||||
// Extensions - Additional extension metadata beyond the base spec.
|
||||
Extensions map[string]interface{} `json:"-"`
|
||||
|
@ -85,7 +83,11 @@ func (ec *EventContextV03) SetExtension(name string, value interface{}) error {
|
|||
if value == nil {
|
||||
delete(ec.Extensions, name)
|
||||
} else {
|
||||
ec.Extensions[name] = value
|
||||
v, err := types.Validate(value)
|
||||
if err == nil {
|
||||
ec.Extensions[name] = v
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -117,7 +119,7 @@ func (ec EventContextV03) AsV02() *EventContextV02 {
|
|||
if ec.Subject != nil {
|
||||
_ = ret.SetExtension(SubjectKey, *ec.Subject)
|
||||
}
|
||||
// DataContentEncoding was introduced in 0.3, so put it in an extension for 0.2.
|
||||
// DeprecatedDataContentEncoding was introduced in 0.3, so put it in an extension for 0.2.
|
||||
if ec.DataContentEncoding != nil {
|
||||
_ = ret.SetExtension(DataContentEncodingKey, *ec.DataContentEncoding)
|
||||
}
|
||||
|
@ -138,6 +140,39 @@ func (ec EventContextV03) AsV03() *EventContextV03 {
|
|||
return &ec
|
||||
}
|
||||
|
||||
// AsV04 implements EventContextConverter.AsV04
|
||||
func (ec EventContextV03) AsV1() *EventContextV1 {
|
||||
ret := EventContextV1{
|
||||
SpecVersion: CloudEventsVersionV1,
|
||||
ID: ec.ID,
|
||||
Time: ec.Time,
|
||||
Type: ec.Type,
|
||||
DataContentType: ec.DataContentType,
|
||||
Source: types.URIRef{URL: ec.Source.URL},
|
||||
Subject: ec.Subject,
|
||||
Extensions: make(map[string]interface{}),
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
ret.DataSchema = &types.URI{URL: ec.SchemaURL.URL}
|
||||
}
|
||||
|
||||
// DataContentEncoding was removed in 1.0, so put it in an extension for 1.0.
|
||||
if ec.DataContentEncoding != nil {
|
||||
_ = ret.SetExtension(DataContentEncodingKey, *ec.DataContentEncoding)
|
||||
}
|
||||
|
||||
if ec.Extensions != nil {
|
||||
for k, v := range ec.Extensions {
|
||||
k = strings.ToLower(k)
|
||||
ret.Extensions[k] = v
|
||||
}
|
||||
}
|
||||
if len(ret.Extensions) == 0 {
|
||||
ret.Extensions = nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// Validate returns errors based on requirements from the CloudEvents spec.
|
||||
// For more details, see https://github.com/cloudevents/spec/blob/master/spec.md
|
||||
// As of Feb 26, 2019, commit 17c32ea26baf7714ad027d9917d03d2fff79fc7e
|
||||
|
|
19
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_reader.go
generated
vendored
19
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_reader.go
generated
vendored
|
@ -1,6 +1,7 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"time"
|
||||
)
|
||||
|
@ -64,22 +65,32 @@ func (ec EventContextV03) GetID() string {
|
|||
return ec.ID
|
||||
}
|
||||
|
||||
// GetSchemaURL implements EventContextReader.GetSchemaURL
|
||||
func (ec EventContextV03) GetSchemaURL() string {
|
||||
// GetDataSchema implements EventContextReader.GetDataSchema
|
||||
func (ec EventContextV03) GetDataSchema() string {
|
||||
if ec.SchemaURL != nil {
|
||||
return ec.SchemaURL.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetDataContentEncoding implements EventContextReader.GetDataContentEncoding
|
||||
func (ec EventContextV03) GetDataContentEncoding() string {
|
||||
// DeprecatedGetDataContentEncoding implements EventContextReader.DeprecatedGetDataContentEncoding
|
||||
func (ec EventContextV03) DeprecatedGetDataContentEncoding() string {
|
||||
if ec.DataContentEncoding != nil {
|
||||
return *ec.DataContentEncoding
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetExtensions implements EventContextReader.GetExtensions
|
||||
func (ec EventContextV03) GetExtensions() map[string]interface{} {
|
||||
return ec.Extensions
|
||||
}
|
||||
|
||||
// GetExtension implements EventContextReader.GetExtension
|
||||
func (ec EventContextV03) GetExtension(key string) (interface{}, error) {
|
||||
v, ok := caseInsensitiveSearch(key, ec.Extensions)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%q not found", key)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
|
8
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_writer.go
generated
vendored
8
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v03_writer.go
generated
vendored
|
@ -81,8 +81,8 @@ func (ec *EventContextV03) SetTime(t time.Time) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetSchemaURL implements EventContextWriter.SetSchemaURL
|
||||
func (ec *EventContextV03) SetSchemaURL(u string) error {
|
||||
// SetDataSchema implements EventContextWriter.SetDataSchema
|
||||
func (ec *EventContextV03) SetDataSchema(u string) error {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
ec.SchemaURL = nil
|
||||
|
@ -96,8 +96,8 @@ func (ec *EventContextV03) SetSchemaURL(u string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetDataContentEncoding implements EventContextWriter.SetDataContentEncoding
|
||||
func (ec *EventContextV03) SetDataContentEncoding(e string) error {
|
||||
// DeprecatedSetDataContentEncoding implements EventContextWriter.DeprecatedSetDataContentEncoding
|
||||
func (ec *EventContextV03) DeprecatedSetDataContentEncoding(e string) error {
|
||||
e = strings.ToLower(strings.TrimSpace(e))
|
||||
if e == "" {
|
||||
ec.DataContentEncoding = nil
|
||||
|
|
300
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v1.go
generated
vendored
Normal file
300
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v1.go
generated
vendored
Normal file
|
@ -0,0 +1,300 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// WIP: AS OF SEP 20, 2019
|
||||
|
||||
const (
|
||||
// CloudEventsVersionV1 represents the version 1.0 of the CloudEvents spec.
|
||||
CloudEventsVersionV1 = "1.0"
|
||||
)
|
||||
|
||||
// EventContextV1 represents the non-data attributes of a CloudEvents v1.0
|
||||
// event.
|
||||
type EventContextV1 struct {
|
||||
// ID of the event; must be non-empty and unique within the scope of the producer.
|
||||
// +required
|
||||
ID string `json:"id"`
|
||||
// Source - A URI describing the event producer.
|
||||
// +required
|
||||
Source types.URIRef `json:"source"`
|
||||
// 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"`
|
||||
|
||||
// DataContentType - A MIME (RFC2046) string describing the media type of `data`.
|
||||
// +optional
|
||||
DataContentType *string `json:"datacontenttype,omitempty"`
|
||||
// Subject - The subject of the event in the context of the event producer
|
||||
// (identified by `source`).
|
||||
// +optional
|
||||
Subject *string `json:"subject,omitempty"`
|
||||
// Time - A Timestamp when the event happened.
|
||||
// +optional
|
||||
Time *types.Timestamp `json:"time,omitempty"`
|
||||
// DataSchema - A link to the schema that the `data` attribute adheres to.
|
||||
// +optional
|
||||
DataSchema *types.URI `json:"dataschema,omitempty"`
|
||||
|
||||
// Extensions - Additional extension metadata beyond the base spec.
|
||||
// +optional
|
||||
Extensions map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// Adhere to EventContext
|
||||
var _ EventContext = (*EventContextV1)(nil)
|
||||
|
||||
// ExtensionAs implements EventContext.ExtensionAs
|
||||
func (ec EventContextV1) ExtensionAs(name string, obj interface{}) error {
|
||||
name = strings.ToLower(name)
|
||||
value, ok := ec.Extensions[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("extension %q does not exist", name)
|
||||
}
|
||||
|
||||
// Only support *string for now.
|
||||
if v, ok := obj.(*string); ok {
|
||||
if *v, ok = value.(string); ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unknown extension type %T", obj)
|
||||
}
|
||||
|
||||
// SetExtension adds the extension 'name' with value 'value' to the CloudEvents context.
|
||||
func (ec *EventContextV1) SetExtension(name string, value interface{}) error {
|
||||
if !IsAlphaNumericLowercaseLetters(name) {
|
||||
return errors.New("bad key, CloudEvents attribute names MUST consist of lower-case letters ('a' to 'z') or digits ('0' to '9') from the ASCII character set")
|
||||
}
|
||||
|
||||
name = strings.ToLower(name)
|
||||
if ec.Extensions == nil {
|
||||
ec.Extensions = make(map[string]interface{})
|
||||
}
|
||||
if value == nil {
|
||||
delete(ec.Extensions, name)
|
||||
return nil
|
||||
} else {
|
||||
v, err := types.Validate(value) // Ensure it's a legal CE attribute value
|
||||
if err == nil {
|
||||
ec.Extensions[name] = v
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Clone implements EventContextConverter.Clone
|
||||
func (ec EventContextV1) Clone() EventContext {
|
||||
return ec.AsV1()
|
||||
}
|
||||
|
||||
// AsV01 implements EventContextConverter.AsV01
|
||||
func (ec EventContextV1) AsV01() *EventContextV01 {
|
||||
ecv2 := ec.AsV02()
|
||||
return ecv2.AsV01()
|
||||
}
|
||||
|
||||
// AsV02 implements EventContextConverter.AsV02
|
||||
func (ec EventContextV1) AsV02() *EventContextV02 {
|
||||
ecv3 := ec.AsV03()
|
||||
return ecv3.AsV02()
|
||||
}
|
||||
|
||||
// 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},
|
||||
Subject: ec.Subject,
|
||||
Extensions: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if ec.DataSchema != nil {
|
||||
ret.SchemaURL = &types.URLRef{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)
|
||||
// DeprecatedDataContentEncoding was introduced in 0.3, removed in 1.0
|
||||
if strings.EqualFold(k, DataContentEncodingKey) {
|
||||
etv, ok := v.(string)
|
||||
if ok && etv != "" {
|
||||
ret.DataContentEncoding = &etv
|
||||
}
|
||||
continue
|
||||
}
|
||||
ret.Extensions[k] = v
|
||||
}
|
||||
}
|
||||
if len(ret.Extensions) == 0 {
|
||||
ret.Extensions = nil
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// AsV04 implements EventContextConverter.AsV04
|
||||
func (ec EventContextV1) AsV1() *EventContextV1 {
|
||||
ec.SpecVersion = CloudEventsVersionV1
|
||||
return &ec
|
||||
}
|
||||
|
||||
// Validate returns errors based on requirements from the CloudEvents spec.
|
||||
// For more details, see https://github.com/cloudevents/spec/blob/v1.0-rc1/spec.md.
|
||||
func (ec EventContextV1) Validate() error {
|
||||
errors := []string(nil)
|
||||
|
||||
// id
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
// MUST be unique within the scope of the producer
|
||||
id := strings.TrimSpace(ec.ID)
|
||||
if id == "" {
|
||||
errors = append(errors, "id: MUST be a non-empty string")
|
||||
// no way to test "MUST be unique within the scope of the producer"
|
||||
}
|
||||
|
||||
// source
|
||||
// Type: URI-reference
|
||||
// Constraints:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty URI-reference
|
||||
// An absolute URI is RECOMMENDED
|
||||
source := strings.TrimSpace(ec.Source.String())
|
||||
if source == "" {
|
||||
errors = append(errors, "source: REQUIRED")
|
||||
}
|
||||
|
||||
// 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:
|
||||
// REQUIRED
|
||||
// MUST be a non-empty string
|
||||
// SHOULD be prefixed with a reverse-DNS name. The prefixed domain dictates the organization which defines the semantics of this event type.
|
||||
eventType := strings.TrimSpace(ec.Type)
|
||||
if eventType == "" {
|
||||
errors = append(errors, "type: MUST be a non-empty string")
|
||||
}
|
||||
|
||||
// The following attributes are optional but still have validation.
|
||||
|
||||
// datacontenttype
|
||||
// Type: String per RFC 2046
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 2046
|
||||
if ec.DataContentType != nil {
|
||||
dataContentType := strings.TrimSpace(*ec.DataContentType)
|
||||
if dataContentType == "" {
|
||||
errors = append(errors, "datacontenttype: if present, MUST adhere to the format specified in RFC 2046")
|
||||
} else {
|
||||
_, _, err := mime.ParseMediaType(dataContentType)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("datacontenttype: failed to parse media type, %s", err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dataschema
|
||||
// Type: URI
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 3986
|
||||
if ec.DataSchema != nil {
|
||||
dataSchema := strings.TrimSpace(ec.DataSchema.String())
|
||||
// empty string is not RFC 3986 compatible.
|
||||
if dataSchema == "" {
|
||||
errors = append(errors, "dataschema: if present, MUST adhere to the format specified in RFC 3986")
|
||||
}
|
||||
}
|
||||
|
||||
// subject
|
||||
// Type: String
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// MUST be a non-empty string
|
||||
if ec.Subject != nil {
|
||||
subject := strings.TrimSpace(*ec.Subject)
|
||||
if subject == "" {
|
||||
errors = append(errors, "subject: if present, MUST be a non-empty string")
|
||||
}
|
||||
}
|
||||
|
||||
// time
|
||||
// Type: Timestamp
|
||||
// Constraints:
|
||||
// OPTIONAL
|
||||
// If present, MUST adhere to the format specified in RFC 3339
|
||||
// --> no need to test this, no way to set the time without it being valid.
|
||||
|
||||
if len(errors) > 0 {
|
||||
return fmt.Errorf(strings.Join(errors, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a pretty-printed representation of the EventContext.
|
||||
func (ec EventContextV1) String() string {
|
||||
b := strings.Builder{}
|
||||
|
||||
b.WriteString("Context Attributes,\n")
|
||||
|
||||
b.WriteString(" specversion: " + ec.SpecVersion + "\n")
|
||||
b.WriteString(" type: " + ec.Type + "\n")
|
||||
b.WriteString(" source: " + ec.Source.String() + "\n")
|
||||
if ec.Subject != nil {
|
||||
b.WriteString(" subject: " + *ec.Subject + "\n")
|
||||
}
|
||||
b.WriteString(" id: " + ec.ID + "\n")
|
||||
if ec.Time != nil {
|
||||
b.WriteString(" time: " + ec.Time.String() + "\n")
|
||||
}
|
||||
if ec.DataSchema != nil {
|
||||
b.WriteString(" dataschema: " + ec.DataSchema.String() + "\n")
|
||||
}
|
||||
if ec.DataContentType != nil {
|
||||
b.WriteString(" datacontenttype: " + *ec.DataContentType + "\n")
|
||||
}
|
||||
|
||||
if ec.Extensions != nil && len(ec.Extensions) > 0 {
|
||||
b.WriteString("Extensions,\n")
|
||||
keys := make([]string, 0, len(ec.Extensions))
|
||||
for k := range ec.Extensions {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
b.WriteString(fmt.Sprintf(" %s: %v\n", key, ec.Extensions[key]))
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
98
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v1_reader.go
generated
vendored
Normal file
98
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v1_reader.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetSpecVersion implements EventContextReader.GetSpecVersion
|
||||
func (ec EventContextV1) GetSpecVersion() string {
|
||||
if ec.SpecVersion != "" {
|
||||
return ec.SpecVersion
|
||||
}
|
||||
return CloudEventsVersionV03
|
||||
}
|
||||
|
||||
// GetDataContentType implements EventContextReader.GetDataContentType
|
||||
func (ec EventContextV1) GetDataContentType() string {
|
||||
if ec.DataContentType != nil {
|
||||
return *ec.DataContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetDataMediaType implements EventContextReader.GetDataMediaType
|
||||
func (ec EventContextV1) GetDataMediaType() (string, error) {
|
||||
if ec.DataContentType != nil {
|
||||
mediaType, _, err := mime.ParseMediaType(*ec.DataContentType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return mediaType, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetType implements EventContextReader.GetType
|
||||
func (ec EventContextV1) GetType() string {
|
||||
return ec.Type
|
||||
}
|
||||
|
||||
// GetSource implements EventContextReader.GetSource
|
||||
func (ec EventContextV1) GetSource() string {
|
||||
return ec.Source.String()
|
||||
}
|
||||
|
||||
// GetSubject implements EventContextReader.GetSubject
|
||||
func (ec EventContextV1) GetSubject() string {
|
||||
if ec.Subject != nil {
|
||||
return *ec.Subject
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetTime implements EventContextReader.GetTime
|
||||
func (ec EventContextV1) GetTime() time.Time {
|
||||
if ec.Time != nil {
|
||||
return ec.Time.Time
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// GetID implements EventContextReader.GetID
|
||||
func (ec EventContextV1) GetID() string {
|
||||
return ec.ID
|
||||
}
|
||||
|
||||
// GetDataSchema implements EventContextReader.GetDataSchema
|
||||
func (ec EventContextV1) GetDataSchema() string {
|
||||
if ec.DataSchema != nil {
|
||||
return ec.DataSchema.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DeprecatedGetDataContentEncoding implements EventContextReader.DeprecatedGetDataContentEncoding
|
||||
func (ec EventContextV1) DeprecatedGetDataContentEncoding() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetExtensions implements EventContextReader.GetExtensions
|
||||
func (ec EventContextV1) GetExtensions() map[string]interface{} {
|
||||
// For now, convert the extensions of v1.0 to the pre-v1.0 style.
|
||||
ext := make(map[string]interface{}, len(ec.Extensions))
|
||||
for k, v := range ec.Extensions {
|
||||
ext[k] = v
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
||||
// GetExtension implements EventContextReader.GetExtension
|
||||
func (ec EventContextV1) GetExtension(key string) (interface{}, error) {
|
||||
v, ok := caseInsensitiveSearch(key, ec.Extensions)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%q not found", key)
|
||||
}
|
||||
return v, nil
|
||||
}
|
102
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v1_writer.go
generated
vendored
Normal file
102
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/eventcontext_v1_writer.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudevents/sdk-go/pkg/cloudevents/types"
|
||||
)
|
||||
|
||||
// Adhere to EventContextWriter
|
||||
var _ EventContextWriter = (*EventContextV1)(nil)
|
||||
|
||||
// SetSpecVersion implements EventContextWriter.SetSpecVersion
|
||||
func (ec *EventContextV1) SetSpecVersion(v string) error {
|
||||
if v != CloudEventsVersionV1 {
|
||||
return fmt.Errorf("invalid version %q, expecting %q", v, CloudEventsVersionV1)
|
||||
}
|
||||
ec.SpecVersion = CloudEventsVersionV1
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataContentType implements EventContextWriter.SetDataContentType
|
||||
func (ec *EventContextV1) SetDataContentType(ct string) error {
|
||||
ct = strings.TrimSpace(ct)
|
||||
if ct == "" {
|
||||
ec.DataContentType = nil
|
||||
} else {
|
||||
ec.DataContentType = &ct
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetType implements EventContextWriter.SetType
|
||||
func (ec *EventContextV1) SetType(t string) error {
|
||||
t = strings.TrimSpace(t)
|
||||
ec.Type = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSource implements EventContextWriter.SetSource
|
||||
func (ec *EventContextV1) SetSource(u string) error {
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.Source = types.URIRef{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSubject implements EventContextWriter.SetSubject
|
||||
func (ec *EventContextV1) SetSubject(s string) error {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
ec.Subject = nil
|
||||
} else {
|
||||
ec.Subject = &s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetID implements EventContextWriter.SetID
|
||||
func (ec *EventContextV1) SetID(id string) error {
|
||||
id = strings.TrimSpace(id)
|
||||
if id == "" {
|
||||
return errors.New("id is required to be a non-empty string")
|
||||
}
|
||||
ec.ID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTime implements EventContextWriter.SetTime
|
||||
func (ec *EventContextV1) SetTime(t time.Time) error {
|
||||
if t.IsZero() {
|
||||
ec.Time = nil
|
||||
} else {
|
||||
ec.Time = &types.Timestamp{Time: t}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDataSchema implements EventContextWriter.SetDataSchema
|
||||
func (ec *EventContextV1) SetDataSchema(u string) error {
|
||||
u = strings.TrimSpace(u)
|
||||
if u == "" {
|
||||
ec.DataSchema = nil
|
||||
return nil
|
||||
}
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ec.DataSchema = &types.URI{URL: *pu}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeprecatedSetDataContentEncoding implements EventContextWriter.DeprecatedSetDataContentEncoding
|
||||
func (ec *EventContextV1) DeprecatedSetDataContentEncoding(e string) error {
|
||||
return errors.New("deprecated: SetDataContentEncoding is not supported in v1.0 of CloudEvents")
|
||||
}
|
|
@ -1,13 +1,30 @@
|
|||
package cloudevents
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// DataContentEncodingKey is the key to DataContentEncoding for versions that do not support data content encoding
|
||||
// DataContentEncodingKey is the key to DeprecatedDataContentEncoding for versions that do not support data content encoding
|
||||
// directly.
|
||||
DataContentEncodingKey = "datacontentencoding"
|
||||
|
||||
// EventTypeVersionKey is the key to EventTypeVersion for versions that do not support event type version directly.
|
||||
EventTypeVersionKey = "eventTypeVersion"
|
||||
EventTypeVersionKey = "eventtypeversion"
|
||||
|
||||
// SubjectKey is the key to Subject for versions that do not support subject directly.
|
||||
SubjectKey = "subject"
|
||||
)
|
||||
|
||||
func caseInsensitiveSearch(key string, space map[string]interface{}) (interface{}, bool) {
|
||||
lkey := strings.ToLower(key)
|
||||
for k, v := range space {
|
||||
if strings.EqualFold(lkey, strings.ToLower(k)) {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var IsAlphaNumericLowercaseLetters = regexp.MustCompile(`^[a-z0-9]+$`).MatchString
|
||||
|
|
|
@ -6,15 +6,17 @@ import "fmt"
|
|||
// message can not be converted.
|
||||
type ErrTransportMessageConversion struct {
|
||||
fatal bool
|
||||
handled bool
|
||||
transport string
|
||||
message string
|
||||
}
|
||||
|
||||
// NewErrMessageEncodingUnknown makes a new ErrMessageEncodingUnknown.
|
||||
func NewErrTransportMessageConversion(transport, message string, fatal bool) *ErrTransportMessageConversion {
|
||||
func NewErrTransportMessageConversion(transport, message string, handled, fatal bool) *ErrTransportMessageConversion {
|
||||
return &ErrTransportMessageConversion{
|
||||
transport: transport,
|
||||
message: message,
|
||||
handled: handled,
|
||||
fatal: fatal,
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +26,11 @@ func (e *ErrTransportMessageConversion) IsFatal() bool {
|
|||
return e.fatal
|
||||
}
|
||||
|
||||
// Handled reports if this error should be considered accepted and no further action.
|
||||
func (e *ErrTransportMessageConversion) Handled() bool {
|
||||
return e.handled
|
||||
}
|
||||
|
||||
// Error implements error.Error
|
||||
func (e *ErrTransportMessageConversion) Error() string {
|
||||
return fmt.Sprintf("transport %s failed to convert message: %s", e.transport, e.message)
|
||||
|
|
|
@ -2,9 +2,12 @@ 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"
|
||||
)
|
||||
|
||||
|
@ -21,152 +24,127 @@ type Codec struct {
|
|||
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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
func (c *Codec) loadCodec(encoding Encoding) (transport.Codec, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV01:
|
||||
fallthrough
|
||||
case StructuredV01:
|
||||
if c.v01 == nil {
|
||||
c.v01 = &CodecV01{Encoding: encoding}
|
||||
}
|
||||
return c.v01.Encode(ctx, e)
|
||||
case BinaryV02:
|
||||
fallthrough
|
||||
case StructuredV02:
|
||||
if c.v02 == nil {
|
||||
c.v02 = &CodecV02{Encoding: encoding}
|
||||
}
|
||||
return c.v02.Encode(ctx, e)
|
||||
case BinaryV03:
|
||||
fallthrough
|
||||
case StructuredV03:
|
||||
if c.v03 == nil {
|
||||
c.v03 = &CodecV03{Encoding: encoding}
|
||||
}
|
||||
return c.v03.Encode(ctx, e)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %s", encoding)
|
||||
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) {
|
||||
switch c.inspectEncoding(ctx, msg) {
|
||||
case BinaryV01, StructuredV01:
|
||||
if c.v01 == nil {
|
||||
c.v01 = &CodecV01{Encoding: c.Encoding}
|
||||
}
|
||||
if event, err := c.v01.Decode(ctx, msg); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return c.convertEvent(event), nil
|
||||
}
|
||||
|
||||
case BinaryV02, StructuredV02:
|
||||
if c.v02 == nil {
|
||||
c.v02 = &CodecV02{Encoding: c.Encoding}
|
||||
}
|
||||
if event, err := c.v02.Decode(ctx, msg); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return c.convertEvent(event), nil
|
||||
}
|
||||
|
||||
case BinaryV03, StructuredV03, BatchedV03:
|
||||
if c.v03 == nil {
|
||||
c.v03 = &CodecV03{Encoding: c.Encoding}
|
||||
}
|
||||
if event, err := c.v03.Decode(ctx, msg); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return c.convertEvent(event), nil
|
||||
}
|
||||
default:
|
||||
return nil, transport.NewErrMessageEncodingUnknown("wrapper", TransportName)
|
||||
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 {
|
||||
func (c *Codec) convertEvent(event *cloudevents.Event) (*cloudevents.Event, error) {
|
||||
if event == nil {
|
||||
return nil
|
||||
return nil, errors.New("event is nil, can not convert")
|
||||
}
|
||||
|
||||
switch c.Encoding {
|
||||
case Default:
|
||||
return event
|
||||
case BinaryV01:
|
||||
fallthrough
|
||||
case StructuredV01:
|
||||
if c.v01 == nil {
|
||||
c.v01 = &CodecV01{Encoding: c.Encoding}
|
||||
}
|
||||
return event, nil
|
||||
case BinaryV01, StructuredV01:
|
||||
ca := event.Context.AsV01()
|
||||
event.Context = ca
|
||||
return event
|
||||
case BinaryV02:
|
||||
fallthrough
|
||||
case StructuredV02:
|
||||
if c.v02 == nil {
|
||||
c.v02 = &CodecV02{Encoding: c.Encoding}
|
||||
}
|
||||
return event, nil
|
||||
case BinaryV02, StructuredV02:
|
||||
ca := event.Context.AsV02()
|
||||
event.Context = ca
|
||||
return event
|
||||
case BinaryV03:
|
||||
fallthrough
|
||||
case StructuredV03:
|
||||
fallthrough
|
||||
case BatchedV03:
|
||||
if c.v03 == nil {
|
||||
c.v03 = &CodecV03{Encoding: c.Encoding}
|
||||
}
|
||||
return event, nil
|
||||
case BinaryV03, StructuredV03, BatchedV03:
|
||||
ca := event.Context.AsV03()
|
||||
event.Context = ca
|
||||
return event
|
||||
return event, nil
|
||||
case BinaryV1, StructuredV1, BatchedV1:
|
||||
ca := event.Context.AsV03()
|
||||
event.Context = ca
|
||||
return event, nil
|
||||
default:
|
||||
return nil
|
||||
return nil, fmt.Errorf("unknown encoding: %s", c.Encoding)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Codec) inspectEncoding(ctx context.Context, msg transport.Message) Encoding {
|
||||
// TODO: there should be a better way to make the version codecs on demand.
|
||||
if c.v01 == nil {
|
||||
c.v01 = &CodecV01{Encoding: c.Encoding}
|
||||
}
|
||||
// Try v0.1 first.
|
||||
encoding := c.v01.inspectEncoding(ctx, msg)
|
||||
// Try v1.0.
|
||||
_, _ = c.loadCodec(BinaryV1)
|
||||
encoding := c.v1.inspectEncoding(ctx, msg)
|
||||
if encoding != Unknown {
|
||||
return encoding
|
||||
}
|
||||
|
||||
if c.v02 == nil {
|
||||
c.v02 = &CodecV02{Encoding: c.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
|
||||
}
|
||||
|
||||
if c.v03 == nil {
|
||||
c.v03 = &CodecV03{Encoding: c.Encoding}
|
||||
}
|
||||
// Try v0.3.
|
||||
encoding = c.v03.inspectEncoding(ctx, msg)
|
||||
// Try v0.1 first.
|
||||
_, _ = c.loadCodec(BinaryV01)
|
||||
encoding = c.v01.inspectEncoding(ctx, msg)
|
||||
if encoding != Unknown {
|
||||
return encoding
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
// CodecStructured represents an structured http transport codec for all versions.
|
||||
// Intended to be used as a base class.
|
||||
type CodecStructured struct {
|
||||
Encoding Encoding
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
func (v CodecStructured) encodeStructured(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
|
|
51
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v01.go
generated
vendored
51
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v01.go
generated
vendored
|
@ -9,16 +9,17 @@ import (
|
|||
"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.3
|
||||
// CodecV01 represents a http transport codec that uses CloudEvents spec v0.1
|
||||
type CodecV01 struct {
|
||||
CodecStructured
|
||||
|
||||
Encoding Encoding
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
// Adheres to Codec
|
||||
|
@ -26,9 +27,19 @@ var _ transport.Codec = (*CodecV01)(nil)
|
|||
|
||||
// Encode implements Codec.Encode
|
||||
func (v CodecV01) Encode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
// TODO: wire context
|
||||
_, r := observability.NewReporter(context.Background(), CodecObserved{o: reportEncode, c: v.Encoding.Codec()})
|
||||
m, err := v.obsEncode(ctx, e)
|
||||
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 {
|
||||
|
@ -37,8 +48,8 @@ func (v CodecV01) Encode(ctx context.Context, e cloudevents.Event) (transport.Me
|
|||
return m, err
|
||||
}
|
||||
|
||||
func (v CodecV01) obsEncode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
switch v.Encoding {
|
||||
func (v CodecV01) obsEncode(ctx context.Context, e cloudevents.Event, encoding Encoding) (transport.Message, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV01:
|
||||
|
@ -46,13 +57,12 @@ func (v CodecV01) obsEncode(ctx context.Context, e cloudevents.Event) (transport
|
|||
case StructuredV01:
|
||||
return v.encodeStructured(ctx, e)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %d", v.Encoding)
|
||||
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) {
|
||||
// TODO: wire context
|
||||
_, 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 {
|
||||
|
@ -107,15 +117,10 @@ func (v CodecV01) toHeaders(ec *cloudevents.EventContextV01) (http.Header, error
|
|||
h["CE-EventTypeVersion"] = []string{*ec.EventTypeVersion}
|
||||
}
|
||||
if ec.SchemaURL != nil {
|
||||
h["CE-SchemaURL"] = []string{ec.SchemaURL.String()}
|
||||
h["CE-DataSchema"] = []string{ec.SchemaURL.String()}
|
||||
}
|
||||
if ec.ContentType != nil {
|
||||
if ec.ContentType != nil && *ec.ContentType != "" {
|
||||
h.Set("Content-Type", *ec.ContentType)
|
||||
} else if v.Encoding == Default || v.Encoding == BinaryV01 {
|
||||
// in binary v0.1, the Content-Type header is tied to ec.ContentType
|
||||
// This was later found to be an issue with the spec, but yolo.
|
||||
// TODO: not sure what the default should be?
|
||||
h.Set("Content-Type", cloudevents.ApplicationJSON)
|
||||
}
|
||||
|
||||
// Regarding Extensions, v0.1 Spec says the following:
|
||||
|
@ -172,17 +177,23 @@ func (v CodecV01) fromHeaders(h http.Header) (cloudevents.EventContextV01, error
|
|||
if source != nil {
|
||||
ec.Source = *source
|
||||
}
|
||||
ec.EventTime = types.ParseTimestamp(h.Get("CE-EventTime"))
|
||||
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-SchemaURL"))
|
||||
h.Del("CE-SchemaURL")
|
||||
ec.SchemaURL = types.ParseURLRef(h.Get("CE-DataSchema"))
|
||||
h.Del("CE-DataSchema")
|
||||
et := h.Get("Content-Type")
|
||||
ec.ContentType = &et
|
||||
if et != "" {
|
||||
ec.ContentType = &et
|
||||
}
|
||||
|
||||
extensions := make(map[string]interface{})
|
||||
for k, v := range h {
|
||||
|
|
39
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v02.go
generated
vendored
39
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v02.go
generated
vendored
|
@ -9,6 +9,7 @@ import (
|
|||
"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"
|
||||
|
@ -18,7 +19,7 @@ import (
|
|||
type CodecV02 struct {
|
||||
CodecStructured
|
||||
|
||||
Encoding Encoding
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
// Adheres to Codec
|
||||
|
@ -26,9 +27,19 @@ var _ transport.Codec = (*CodecV02)(nil)
|
|||
|
||||
// Encode implements Codec.Encode
|
||||
func (v CodecV02) Encode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
// TODO: wire context
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportEncode, c: v.Encoding.Codec()})
|
||||
m, err := v.obsEncode(ctx, e)
|
||||
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 {
|
||||
|
@ -37,8 +48,8 @@ func (v CodecV02) Encode(ctx context.Context, e cloudevents.Event) (transport.Me
|
|||
return m, err
|
||||
}
|
||||
|
||||
func (v CodecV02) obsEncode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
switch v.Encoding {
|
||||
func (v CodecV02) obsEncode(ctx context.Context, e cloudevents.Event, encoding Encoding) (transport.Message, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV02:
|
||||
|
@ -46,13 +57,12 @@ func (v CodecV02) obsEncode(ctx context.Context, e cloudevents.Event) (transport
|
|||
case StructuredV02:
|
||||
return v.encodeStructured(ctx, e)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %d", v.Encoding)
|
||||
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) {
|
||||
// TODO: wire context
|
||||
_, 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 {
|
||||
|
@ -104,13 +114,8 @@ func (v CodecV02) toHeaders(ec *cloudevents.EventContextV02) (http.Header, error
|
|||
if ec.SchemaURL != nil {
|
||||
h.Set("ce-schemaurl", ec.SchemaURL.String())
|
||||
}
|
||||
if ec.ContentType != nil {
|
||||
if ec.ContentType != nil && *ec.ContentType != "" {
|
||||
h.Set("Content-Type", *ec.ContentType)
|
||||
} else if v.Encoding == Default || v.Encoding == BinaryV02 {
|
||||
// in binary v0.2, the Content-Type header is tied to ec.ContentType
|
||||
// This was later found to be an issue with the spec, but yolo.
|
||||
// TODO: not sure what the default should be?
|
||||
h.Set("Content-Type", cloudevents.ApplicationJSON)
|
||||
}
|
||||
for k, v := range ec.Extensions {
|
||||
// Per spec, map-valued extensions are converted to a list of headers as:
|
||||
|
@ -182,7 +187,11 @@ func (v CodecV02) fromHeaders(h http.Header) (cloudevents.EventContextV02, error
|
|||
}
|
||||
h.Del("ce-source")
|
||||
|
||||
ec.Time = types.ParseTimestamp(h.Get("ce-time"))
|
||||
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"))
|
||||
|
|
41
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v03.go
generated
vendored
41
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v03.go
generated
vendored
|
@ -9,6 +9,7 @@ import (
|
|||
"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"
|
||||
|
@ -18,7 +19,7 @@ import (
|
|||
type CodecV03 struct {
|
||||
CodecStructured
|
||||
|
||||
Encoding Encoding
|
||||
DefaultEncoding Encoding
|
||||
}
|
||||
|
||||
// Adheres to Codec
|
||||
|
@ -26,9 +27,19 @@ var _ transport.Codec = (*CodecV03)(nil)
|
|||
|
||||
// Encode implements Codec.Encode
|
||||
func (v CodecV03) Encode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
// TODO: wire context
|
||||
_, r := observability.NewReporter(ctx, CodecObserved{o: reportEncode, c: v.Encoding.Codec()})
|
||||
m, err := v.obsEncode(ctx, e)
|
||||
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 {
|
||||
|
@ -37,8 +48,8 @@ func (v CodecV03) Encode(ctx context.Context, e cloudevents.Event) (transport.Me
|
|||
return m, err
|
||||
}
|
||||
|
||||
func (v CodecV03) obsEncode(ctx context.Context, e cloudevents.Event) (transport.Message, error) {
|
||||
switch v.Encoding {
|
||||
func (v CodecV03) obsEncode(ctx context.Context, e cloudevents.Event, encoding Encoding) (transport.Message, error) {
|
||||
switch encoding {
|
||||
case Default:
|
||||
fallthrough
|
||||
case BinaryV03:
|
||||
|
@ -48,13 +59,12 @@ func (v CodecV03) obsEncode(ctx context.Context, e cloudevents.Event) (transport
|
|||
case BatchedV03:
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown encoding: %d", v.Encoding)
|
||||
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) {
|
||||
// TODO: wire context
|
||||
_, 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 {
|
||||
|
@ -112,19 +122,15 @@ func (v CodecV03) toHeaders(ec *cloudevents.EventContextV03) (http.Header, error
|
|||
if ec.SchemaURL != nil {
|
||||
h.Set("ce-schemaurl", ec.SchemaURL.String())
|
||||
}
|
||||
if ec.DataContentType != nil {
|
||||
if ec.DataContentType != nil && *ec.DataContentType != "" {
|
||||
h.Set("Content-Type", *ec.DataContentType)
|
||||
} else if v.Encoding == Default || v.Encoding == BinaryV03 {
|
||||
// in binary v0.2, the Content-Type header is tied to ec.ContentType
|
||||
// This was later found to be an issue with the spec, but yolo.
|
||||
// TODO: not sure what the default should be?
|
||||
h.Set("Content-Type", cloudevents.ApplicationJSON)
|
||||
}
|
||||
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) {
|
||||
|
@ -212,7 +218,11 @@ func (v CodecV03) fromHeaders(h http.Header) (cloudevents.EventContextV03, error
|
|||
}
|
||||
h.Del("ce-subject")
|
||||
|
||||
ec.Time = types.ParseTimestamp(h.Get("ce-time"))
|
||||
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"))
|
||||
|
@ -235,6 +245,7 @@ func (v CodecV03) fromHeaders(h http.Header) (cloudevents.EventContextV03, error
|
|||
|
||||
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 {
|
||||
|
|
245
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v1.go
generated
vendored
Normal file
245
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/codec_v1.go
generated
vendored
Normal file
|
@ -0,0 +1,245 @@
|
|||
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
|
||||
}
|
71
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/encoding.go
generated
vendored
71
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/encoding.go
generated
vendored
|
@ -29,6 +29,13 @@ const (
|
|||
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
|
||||
|
||||
|
@ -39,6 +46,10 @@ const (
|
|||
// 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 {
|
||||
|
@ -62,6 +73,8 @@ func DefaultBinaryEncodingSelectionStrategy(ctx context.Context, e cloudevents.E
|
|||
return BinaryV02
|
||||
case cloudevents.CloudEventsVersionV03:
|
||||
return BinaryV03
|
||||
case cloudevents.CloudEventsVersionV1:
|
||||
return BinaryV1
|
||||
}
|
||||
// Unknown version, return Default.
|
||||
return Default
|
||||
|
@ -77,6 +90,8 @@ func DefaultStructuredEncodingSelectionStrategy(ctx context.Context, e cloudeven
|
|||
return StructuredV02
|
||||
case cloudevents.CloudEventsVersionV03:
|
||||
return StructuredV03
|
||||
case cloudevents.CloudEventsVersionV1:
|
||||
return StructuredV1
|
||||
}
|
||||
// Unknown version, return Default.
|
||||
return Default
|
||||
|
@ -89,23 +104,15 @@ func (e Encoding) String() string {
|
|||
return "Default Encoding " + e.Version()
|
||||
|
||||
// Binary
|
||||
case BinaryV01:
|
||||
fallthrough
|
||||
case BinaryV02:
|
||||
fallthrough
|
||||
case BinaryV03:
|
||||
case BinaryV01, BinaryV02, BinaryV03, BinaryV1:
|
||||
return "Binary Encoding " + e.Version()
|
||||
|
||||
// Structured
|
||||
case StructuredV01:
|
||||
fallthrough
|
||||
case StructuredV02:
|
||||
fallthrough
|
||||
case StructuredV03:
|
||||
case StructuredV01, StructuredV02, StructuredV03, StructuredV1:
|
||||
return "Structured Encoding " + e.Version()
|
||||
|
||||
// Batched
|
||||
case BatchedV03:
|
||||
case BatchedV03, BatchedV1:
|
||||
return "Batched Encoding " + e.Version()
|
||||
|
||||
default:
|
||||
|
@ -120,25 +127,21 @@ func (e Encoding) Version() string {
|
|||
return "Default"
|
||||
|
||||
// Version 0.1
|
||||
case BinaryV01:
|
||||
fallthrough
|
||||
case StructuredV01:
|
||||
case BinaryV01, StructuredV01:
|
||||
return "v0.1"
|
||||
|
||||
// Version 0.2
|
||||
case BinaryV02:
|
||||
fallthrough
|
||||
case StructuredV02:
|
||||
case BinaryV02, StructuredV02:
|
||||
return "v0.2"
|
||||
|
||||
// Version 0.3
|
||||
case BinaryV03:
|
||||
fallthrough
|
||||
case StructuredV03:
|
||||
fallthrough
|
||||
case BatchedV03:
|
||||
case BinaryV03, StructuredV03, BatchedV03:
|
||||
return "v0.3"
|
||||
|
||||
// Version 1.0
|
||||
case BinaryV1, StructuredV1, BatchedV1:
|
||||
return "v1.0"
|
||||
|
||||
// Unknown
|
||||
default:
|
||||
return "Unknown"
|
||||
|
@ -171,8 +174,32 @@ func (e Encoding) Codec() string {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -178,7 +178,7 @@ func WithPort(port int) Option {
|
|||
if t == nil {
|
||||
return fmt.Errorf("http port option can not set nil transport")
|
||||
}
|
||||
if port < 0 {
|
||||
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 {
|
||||
|
|
38
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/transport.go
generated
vendored
38
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/transport/http/transport.go
generated
vendored
|
@ -181,6 +181,7 @@ func (t *Transport) obsSend(ctx context.Context, event cloudevents.Event) (conte
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -214,14 +215,21 @@ func (t *Transport) obsSend(ctx context.Context, event cloudevents.Event) (conte
|
|||
})
|
||||
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
|
||||
|
@ -240,13 +248,13 @@ func (t *Transport) MessageToEvent(ctx context.Context, msg *Message) (*cloudeve
|
|||
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), true)
|
||||
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)
|
||||
err = transport.NewErrTransportMessageConversion("http", "cloudevents version unknown", false, false)
|
||||
}
|
||||
|
||||
// If codec returns and error, or could not load the correct codec, try
|
||||
|
@ -254,12 +262,19 @@ func (t *Transport) MessageToEvent(ctx context.Context, msg *Message) (*cloudeve
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -548,16 +563,30 @@ func (t *Transport) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
})
|
||||
if err != nil {
|
||||
isFatal := true
|
||||
handled := false
|
||||
if txerr, ok := err.(*transport.ErrTransportMessageConversion); ok {
|
||||
isFatal = txerr.IsFatal()
|
||||
handled = txerr.Handled()
|
||||
}
|
||||
if isFatal || event == nil {
|
||||
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)
|
||||
|
@ -623,6 +652,9 @@ func (t *Transport) listen() (net.Addr, error) {
|
|||
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 {
|
||||
|
|
|
@ -1,4 +1,41 @@
|
|||
/*
|
||||
Package types provides custom types to support CloudEvents.
|
||||
Package types implements the CloudEvents type system.
|
||||
|
||||
CloudEvents defines a set of abstract types for event context attributes. Each
|
||||
type has a corresponding native Go type and a canonical string encoding. The
|
||||
native Go types used to represent the CloudEvents types are:
|
||||
bool, int32, string, []byte, *url.URL, time.Time
|
||||
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|CloudEvents Type|Native Type |Convertible From |
|
||||
+================+================+===================================+
|
||||
|Bool |bool |bool |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|Integer |int32 |Any numeric type with value in |
|
||||
| | |range of int32 |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|String |string |string |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|Binary |[]byte |[]byte |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|URI-Reference |*url.URL |url.URL, types.URIRef, types.URI |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|URI |*url.URL |url.URL, types.URIRef, types.URI |
|
||||
| | |Must be an absolute URI. |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|Timestamp |time.Time |time.Time, types.Timestamp |
|
||||
+----------------+----------------+-----------------------------------+
|
||||
|
||||
Extension attributes may be stored as a native type or a canonical string. The
|
||||
To<Type> functions will convert to the desired <Type> from any convertible type
|
||||
or from the canonical string form.
|
||||
|
||||
The Parse<Type> and Format<Type> functions convert native types to/from
|
||||
canonical strings.
|
||||
|
||||
Note are no Parse or Format functions for URL or string. For URL use the
|
||||
standard url.Parse() and url.URL.String(). The canonical string format of a
|
||||
string is the string itself.
|
||||
|
||||
*/
|
||||
package types
|
||||
|
|
|
@ -16,15 +16,12 @@ type Timestamp struct {
|
|||
}
|
||||
|
||||
// ParseTimestamp attempts to parse the given time assuming RFC3339 layout
|
||||
func ParseTimestamp(t string) *Timestamp {
|
||||
if t == "" {
|
||||
return nil
|
||||
func ParseTimestamp(s string) (*Timestamp, error) {
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
timestamp, err := time.Parse(time.RFC3339Nano, t)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &Timestamp{Time: timestamp}
|
||||
tt, err := ParseTime(s)
|
||||
return &Timestamp{Time: tt}, err
|
||||
}
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
|
@ -33,8 +30,7 @@ func (t *Timestamp) MarshalJSON() ([]byte, error) {
|
|||
if t == nil || t.IsZero() {
|
||||
return []byte(`""`), nil
|
||||
}
|
||||
rfc3339 := fmt.Sprintf("%q", t.UTC().Format(time.RFC3339Nano))
|
||||
return []byte(rfc3339), nil
|
||||
return []byte(fmt.Sprintf("%q", t)), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json unmarshal method used when this type is
|
||||
|
@ -44,10 +40,9 @@ func (t *Timestamp) UnmarshalJSON(b []byte) error {
|
|||
if err := json.Unmarshal(b, ×tamp); err != nil {
|
||||
return err
|
||||
}
|
||||
if pt := ParseTimestamp(timestamp); pt != nil {
|
||||
*t = *pt
|
||||
}
|
||||
return nil
|
||||
var err error
|
||||
t.Time, err = ParseTime(timestamp)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalXML implements a custom xml marshal method used when this type is
|
||||
|
@ -56,8 +51,7 @@ func (t *Timestamp) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||
if t == nil || t.IsZero() {
|
||||
return e.EncodeElement(nil, start)
|
||||
}
|
||||
v := t.UTC().Format(time.RFC3339Nano)
|
||||
return e.EncodeElement(v, start)
|
||||
return e.EncodeElement(t.String(), start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements the xml unmarshal method used when this type is
|
||||
|
@ -67,17 +61,10 @@ func (t *Timestamp) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||
if err := d.DecodeElement(×tamp, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
if pt := ParseTimestamp(timestamp); pt != nil {
|
||||
*t = *pt
|
||||
}
|
||||
return nil
|
||||
var err error
|
||||
t.Time, err = ParseTime(timestamp)
|
||||
return err
|
||||
}
|
||||
|
||||
// String outputs the time using layout RFC3339.
|
||||
func (t *Timestamp) String() string {
|
||||
if t == nil {
|
||||
return time.Time{}.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
return t.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
// String outputs the time using RFC3339 format.
|
||||
func (t Timestamp) String() string { return FormatTime(t.Time) }
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// URI is a wrapper to url.URL. It is intended to enforce compliance with
|
||||
// the CloudEvents spec for their definition of URI. Custom
|
||||
// marshal methods are implemented to ensure the outbound URI object
|
||||
// is a flat string.
|
||||
type URI struct {
|
||||
url.URL
|
||||
}
|
||||
|
||||
// ParseURI attempts to parse the given string as a URI.
|
||||
func ParseURI(u string) *URI {
|
||||
if u == "" {
|
||||
return nil
|
||||
}
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &URI{URL: *pu}
|
||||
}
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
// marshaled using json.Marshal.
|
||||
func (u URI) MarshalJSON() ([]byte, error) {
|
||||
b := fmt.Sprintf("%q", u.String())
|
||||
return []byte(b), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json unmarshal method used when this type is
|
||||
// unmarshaled using json.Unmarshal.
|
||||
func (u *URI) UnmarshalJSON(b []byte) error {
|
||||
var ref string
|
||||
if err := json.Unmarshal(b, &ref); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURI(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML implements a custom xml marshal method used when this type is
|
||||
// marshaled using xml.Marshal.
|
||||
func (u URI) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return e.EncodeElement(u.String(), start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements the xml unmarshal method used when this type is
|
||||
// unmarshaled using xml.Unmarshal.
|
||||
func (u *URI) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var ref string
|
||||
if err := d.DecodeElement(&ref, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURI(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the full string representation of the URI-Reference.
|
||||
func (u *URI) String() string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
return u.URL.String()
|
||||
}
|
77
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/uriref.go
generated
vendored
Normal file
77
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/uriref.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// URIRef is a wrapper to url.URL. It is intended to enforce compliance with
|
||||
// the CloudEvents spec for their definition of URI-Reference. Custom
|
||||
// marshal methods are implemented to ensure the outbound URIRef object is
|
||||
// is a flat string.
|
||||
type URIRef struct {
|
||||
url.URL
|
||||
}
|
||||
|
||||
// ParseURIRef attempts to parse the given string as a URI-Reference.
|
||||
func ParseURIRef(u string) *URIRef {
|
||||
if u == "" {
|
||||
return nil
|
||||
}
|
||||
pu, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &URIRef{URL: *pu}
|
||||
}
|
||||
|
||||
// MarshalJSON implements a custom json marshal method used when this type is
|
||||
// marshaled using json.Marshal.
|
||||
func (u URIRef) MarshalJSON() ([]byte, error) {
|
||||
b := fmt.Sprintf("%q", u.String())
|
||||
return []byte(b), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json unmarshal method used when this type is
|
||||
// unmarshaled using json.Unmarshal.
|
||||
func (u *URIRef) UnmarshalJSON(b []byte) error {
|
||||
var ref string
|
||||
if err := json.Unmarshal(b, &ref); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURIRef(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalXML implements a custom xml marshal method used when this type is
|
||||
// marshaled using xml.Marshal.
|
||||
func (u URIRef) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
return e.EncodeElement(u.String(), start)
|
||||
}
|
||||
|
||||
// UnmarshalXML implements the xml unmarshal method used when this type is
|
||||
// unmarshaled using xml.Unmarshal.
|
||||
func (u *URIRef) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var ref string
|
||||
if err := d.DecodeElement(&ref, &start); err != nil {
|
||||
return err
|
||||
}
|
||||
r := ParseURIRef(ref)
|
||||
if r != nil {
|
||||
*u = *r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the full string representation of the URI-Reference.
|
||||
func (u *URIRef) String() string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
return u.URL.String()
|
||||
}
|
|
@ -11,6 +11,8 @@ import (
|
|||
// 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
|
||||
}
|
||||
|
|
260
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/value.go
generated
vendored
Normal file
260
vendor/github.com/cloudevents/sdk-go/pkg/cloudevents/types/value.go
generated
vendored
Normal file
|
@ -0,0 +1,260 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FormatBool returns canonical string format: "true" or "false"
|
||||
func FormatBool(v bool) string { return strconv.FormatBool(v) }
|
||||
|
||||
// FormatInteger returns canonical string format: decimal notation.
|
||||
func FormatInteger(v int32) string { return strconv.Itoa(int(v)) }
|
||||
|
||||
// FormatBinary returns canonical string format: standard base64 encoding
|
||||
func FormatBinary(v []byte) string { return base64.StdEncoding.EncodeToString(v) }
|
||||
|
||||
// FormatTime returns canonical string format: RFC3339 with nanoseconds
|
||||
func FormatTime(v time.Time) string { return v.UTC().Format(time.RFC3339Nano) }
|
||||
|
||||
// ParseBool parse canonical string format: "true" or "false"
|
||||
func ParseBool(v string) (bool, error) { return strconv.ParseBool(v) }
|
||||
|
||||
// ParseInteger parse canonical string format: decimal notation.
|
||||
func ParseInteger(v string) (int32, error) {
|
||||
// Accept floating-point but truncate to int32 as per CE spec.
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if f > math.MaxInt32 || f < math.MinInt32 {
|
||||
return 0, rangeErr(v)
|
||||
}
|
||||
return int32(f), nil
|
||||
}
|
||||
|
||||
// ParseBinary parse canonical string format: standard base64 encoding
|
||||
func ParseBinary(v string) ([]byte, error) { return base64.StdEncoding.DecodeString(v) }
|
||||
|
||||
// ParseTime parse canonical string format: RFC3339 with nanoseconds
|
||||
func ParseTime(v string) (time.Time, error) {
|
||||
t, err := time.Parse(time.RFC3339Nano, v)
|
||||
if err != nil {
|
||||
err := convertErr(time.Time{}, v)
|
||||
err.extra = ": not in RFC3339 format"
|
||||
return time.Time{}, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Format returns the canonical string format of v, where v can be
|
||||
// any type that is convertible to a CloudEvents type.
|
||||
func Format(v interface{}) (string, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case bool:
|
||||
return FormatBool(v), nil
|
||||
case int32:
|
||||
return FormatInteger(v), nil
|
||||
case string:
|
||||
return v, nil
|
||||
case []byte:
|
||||
return FormatBinary(v), nil
|
||||
case url.URL:
|
||||
return v.String(), nil
|
||||
case *url.URL:
|
||||
// url.URL is often passed by pointer so allow both
|
||||
return v.String(), nil
|
||||
case time.Time:
|
||||
return FormatTime(v), nil
|
||||
default:
|
||||
return "", fmt.Errorf("%T is not a CloudEvents type", v)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate v is a valid CloudEvents attribute value, convert it to one of:
|
||||
// bool, int32, string, []byte, *url.URL, time.Time
|
||||
func Validate(v interface{}) (interface{}, error) {
|
||||
switch v := v.(type) {
|
||||
case bool, int32, string, []byte, time.Time:
|
||||
return v, nil // Already a CloudEvents type, no validation needed.
|
||||
|
||||
case uint, uintptr, uint8, uint16, uint32, uint64:
|
||||
u := reflect.ValueOf(v).Uint()
|
||||
if u > math.MaxInt32 {
|
||||
return nil, rangeErr(v)
|
||||
}
|
||||
return int32(u), nil
|
||||
case int, int8, int16, int64:
|
||||
i := reflect.ValueOf(v).Int()
|
||||
if i > math.MaxInt32 || i < math.MinInt32 {
|
||||
return nil, rangeErr(v)
|
||||
}
|
||||
return int32(i), nil
|
||||
case float32, float64:
|
||||
f := reflect.ValueOf(v).Float()
|
||||
if f > math.MaxInt32 || f < math.MinInt32 {
|
||||
return nil, rangeErr(v)
|
||||
}
|
||||
return int32(f), nil
|
||||
|
||||
case *url.URL:
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
return v, nil
|
||||
case url.URL:
|
||||
return &v, nil
|
||||
case URIRef:
|
||||
return &v.URL, nil
|
||||
case URI:
|
||||
return &v.URL, nil
|
||||
case URLRef:
|
||||
return &v.URL, nil
|
||||
|
||||
case Timestamp:
|
||||
return v.Time, nil
|
||||
}
|
||||
rx := reflect.ValueOf(v)
|
||||
if rx.Kind() == reflect.Ptr && !rx.IsNil() {
|
||||
// Allow pointers-to convertible types
|
||||
return Validate(rx.Elem().Interface())
|
||||
}
|
||||
return nil, fmt.Errorf("invalid CloudEvents value: %#v", v)
|
||||
}
|
||||
|
||||
// ToBool accepts a bool value or canonical "true"/"false" string.
|
||||
func ToBool(v interface{}) (bool, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case bool:
|
||||
return v, nil
|
||||
case string:
|
||||
return ParseBool(v)
|
||||
default:
|
||||
return false, convertErr(true, v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToInteger accepts any numeric value in int32 range, or canonical string.
|
||||
func ToInteger(v interface{}) (int32, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case int32:
|
||||
return v, nil
|
||||
case string:
|
||||
return ParseInteger(v)
|
||||
default:
|
||||
return 0, convertErr(int32(0), v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToString returns a string value unaltered.
|
||||
//
|
||||
// This function does not perform canonical string encoding, use one of the
|
||||
// Format functions for that.
|
||||
func ToString(v interface{}) (string, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return v, nil
|
||||
default:
|
||||
return "", convertErr("", v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToBinary returns a []byte value, decoding from base64 string if necessary.
|
||||
func ToBinary(v interface{}) ([]byte, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
return v, nil
|
||||
case string:
|
||||
return base64.StdEncoding.DecodeString(v)
|
||||
default:
|
||||
return nil, convertErr([]byte(nil), v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToURL returns a *url.URL value, parsing from string if necessary.
|
||||
func ToURL(v interface{}) (*url.URL, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case *url.URL:
|
||||
return v, nil
|
||||
case string:
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
default:
|
||||
return nil, convertErr((*url.URL)(nil), v)
|
||||
}
|
||||
}
|
||||
|
||||
// ToTime returns a time.Time value, parsing from RFC3339 string if necessary.
|
||||
func ToTime(v interface{}) (time.Time, error) {
|
||||
v, err := Validate(v)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case time.Time:
|
||||
return v, nil
|
||||
case string:
|
||||
ts, err := time.Parse(time.RFC3339Nano, v)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return ts, nil
|
||||
default:
|
||||
return time.Time{}, convertErr(time.Time{}, v)
|
||||
}
|
||||
}
|
||||
|
||||
type ConvertErr struct {
|
||||
// Value being converted
|
||||
Value interface{}
|
||||
// Type of attempted conversion
|
||||
Type reflect.Type
|
||||
|
||||
extra string
|
||||
}
|
||||
|
||||
func (e *ConvertErr) Error() string {
|
||||
return fmt.Sprintf("cannot convert %#v to %s%s", e.Value, e.Type, e.extra)
|
||||
}
|
||||
|
||||
func convertErr(target, v interface{}) *ConvertErr {
|
||||
return &ConvertErr{Value: v, Type: reflect.TypeOf(target)}
|
||||
}
|
||||
|
||||
func rangeErr(v interface{}) error {
|
||||
e := convertErr(int32(0), v)
|
||||
e.extra = ": out of range"
|
||||
return e
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2013 Kelsey Hightower
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the MIT License that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
// Package envconfig implements decoding of environment variables based on a user
|
||||
// defined specification. A typical use is using environment variables for
|
||||
// configuration settings.
|
||||
package envconfig
|
|
@ -0,0 +1,7 @@
|
|||
// +build appengine go1.5
|
||||
|
||||
package envconfig
|
||||
|
||||
import "os"
|
||||
|
||||
var lookupEnv = os.LookupEnv
|
|
@ -0,0 +1,7 @@
|
|||
// +build !appengine,!go1.5
|
||||
|
||||
package envconfig
|
||||
|
||||
import "syscall"
|
||||
|
||||
var lookupEnv = syscall.Getenv
|
|
@ -0,0 +1,382 @@
|
|||
// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
|
||||
// Use of this source code is governed by the MIT License that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package envconfig
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrInvalidSpecification indicates that a specification is of the wrong type.
|
||||
var ErrInvalidSpecification = errors.New("specification must be a struct pointer")
|
||||
|
||||
var gatherRegexp = regexp.MustCompile("([^A-Z]+|[A-Z]+[^A-Z]+|[A-Z]+)")
|
||||
var acronymRegexp = regexp.MustCompile("([A-Z]+)([A-Z][^A-Z]+)")
|
||||
|
||||
// A ParseError occurs when an environment variable cannot be converted to
|
||||
// the type required by a struct field during assignment.
|
||||
type ParseError struct {
|
||||
KeyName string
|
||||
FieldName string
|
||||
TypeName string
|
||||
Value string
|
||||
Err error
|
||||
}
|
||||
|
||||
// Decoder has the same semantics as Setter, but takes higher precedence.
|
||||
// It is provided for historical compatibility.
|
||||
type Decoder interface {
|
||||
Decode(value string) error
|
||||
}
|
||||
|
||||
// Setter is implemented by types can self-deserialize values.
|
||||
// Any type that implements flag.Value also implements Setter.
|
||||
type Setter interface {
|
||||
Set(value string) error
|
||||
}
|
||||
|
||||
func (e *ParseError) Error() string {
|
||||
return fmt.Sprintf("envconfig.Process: assigning %[1]s to %[2]s: converting '%[3]s' to type %[4]s. details: %[5]s", e.KeyName, e.FieldName, e.Value, e.TypeName, e.Err)
|
||||
}
|
||||
|
||||
// varInfo maintains information about the configuration variable
|
||||
type varInfo struct {
|
||||
Name string
|
||||
Alt string
|
||||
Key string
|
||||
Field reflect.Value
|
||||
Tags reflect.StructTag
|
||||
}
|
||||
|
||||
// GatherInfo gathers information about the specified struct
|
||||
func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) {
|
||||
s := reflect.ValueOf(spec)
|
||||
|
||||
if s.Kind() != reflect.Ptr {
|
||||
return nil, ErrInvalidSpecification
|
||||
}
|
||||
s = s.Elem()
|
||||
if s.Kind() != reflect.Struct {
|
||||
return nil, ErrInvalidSpecification
|
||||
}
|
||||
typeOfSpec := s.Type()
|
||||
|
||||
// over allocate an info array, we will extend if needed later
|
||||
infos := make([]varInfo, 0, s.NumField())
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
f := s.Field(i)
|
||||
ftype := typeOfSpec.Field(i)
|
||||
if !f.CanSet() || isTrue(ftype.Tag.Get("ignored")) {
|
||||
continue
|
||||
}
|
||||
|
||||
for f.Kind() == reflect.Ptr {
|
||||
if f.IsNil() {
|
||||
if f.Type().Elem().Kind() != reflect.Struct {
|
||||
// nil pointer to a non-struct: leave it alone
|
||||
break
|
||||
}
|
||||
// nil pointer to struct: create a zero instance
|
||||
f.Set(reflect.New(f.Type().Elem()))
|
||||
}
|
||||
f = f.Elem()
|
||||
}
|
||||
|
||||
// Capture information about the config variable
|
||||
info := varInfo{
|
||||
Name: ftype.Name,
|
||||
Field: f,
|
||||
Tags: ftype.Tag,
|
||||
Alt: strings.ToUpper(ftype.Tag.Get("envconfig")),
|
||||
}
|
||||
|
||||
// Default to the field name as the env var name (will be upcased)
|
||||
info.Key = info.Name
|
||||
|
||||
// Best effort to un-pick camel casing as separate words
|
||||
if isTrue(ftype.Tag.Get("split_words")) {
|
||||
words := gatherRegexp.FindAllStringSubmatch(ftype.Name, -1)
|
||||
if len(words) > 0 {
|
||||
var name []string
|
||||
for _, words := range words {
|
||||
if m := acronymRegexp.FindStringSubmatch(words[0]); len(m) == 3 {
|
||||
name = append(name, m[1], m[2])
|
||||
} else {
|
||||
name = append(name, words[0])
|
||||
}
|
||||
}
|
||||
|
||||
info.Key = strings.Join(name, "_")
|
||||
}
|
||||
}
|
||||
if info.Alt != "" {
|
||||
info.Key = info.Alt
|
||||
}
|
||||
if prefix != "" {
|
||||
info.Key = fmt.Sprintf("%s_%s", prefix, info.Key)
|
||||
}
|
||||
info.Key = strings.ToUpper(info.Key)
|
||||
infos = append(infos, info)
|
||||
|
||||
if f.Kind() == reflect.Struct {
|
||||
// honor Decode if present
|
||||
if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil && binaryUnmarshaler(f) == nil {
|
||||
innerPrefix := prefix
|
||||
if !ftype.Anonymous {
|
||||
innerPrefix = info.Key
|
||||
}
|
||||
|
||||
embeddedPtr := f.Addr().Interface()
|
||||
embeddedInfos, err := gatherInfo(innerPrefix, embeddedPtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
infos = append(infos[:len(infos)-1], embeddedInfos...)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// CheckDisallowed checks that no environment variables with the prefix are set
|
||||
// that we don't know how or want to parse. This is likely only meaningful with
|
||||
// a non-empty prefix.
|
||||
func CheckDisallowed(prefix string, spec interface{}) error {
|
||||
infos, err := gatherInfo(prefix, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vars := make(map[string]struct{})
|
||||
for _, info := range infos {
|
||||
vars[info.Key] = struct{}{}
|
||||
}
|
||||
|
||||
if prefix != "" {
|
||||
prefix = strings.ToUpper(prefix) + "_"
|
||||
}
|
||||
|
||||
for _, env := range os.Environ() {
|
||||
if !strings.HasPrefix(env, prefix) {
|
||||
continue
|
||||
}
|
||||
v := strings.SplitN(env, "=", 2)[0]
|
||||
if _, found := vars[v]; !found {
|
||||
return fmt.Errorf("unknown environment variable %s", v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process populates the specified struct based on environment variables
|
||||
func Process(prefix string, spec interface{}) error {
|
||||
infos, err := gatherInfo(prefix, spec)
|
||||
|
||||
for _, info := range infos {
|
||||
|
||||
// `os.Getenv` cannot differentiate between an explicitly set empty value
|
||||
// and an unset value. `os.LookupEnv` is preferred to `syscall.Getenv`,
|
||||
// but it is only available in go1.5 or newer. We're using Go build tags
|
||||
// here to use os.LookupEnv for >=go1.5
|
||||
value, ok := lookupEnv(info.Key)
|
||||
if !ok && info.Alt != "" {
|
||||
value, ok = lookupEnv(info.Alt)
|
||||
}
|
||||
|
||||
def := info.Tags.Get("default")
|
||||
if def != "" && !ok {
|
||||
value = def
|
||||
}
|
||||
|
||||
req := info.Tags.Get("required")
|
||||
if !ok && def == "" {
|
||||
if isTrue(req) {
|
||||
key := info.Key
|
||||
if info.Alt != "" {
|
||||
key = info.Alt
|
||||
}
|
||||
return fmt.Errorf("required key %s missing value", key)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err = processField(value, info.Field)
|
||||
if err != nil {
|
||||
return &ParseError{
|
||||
KeyName: info.Key,
|
||||
FieldName: info.Name,
|
||||
TypeName: info.Field.Type().String(),
|
||||
Value: value,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// MustProcess is the same as Process but panics if an error occurs
|
||||
func MustProcess(prefix string, spec interface{}) {
|
||||
if err := Process(prefix, spec); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func processField(value string, field reflect.Value) error {
|
||||
typ := field.Type()
|
||||
|
||||
decoder := decoderFrom(field)
|
||||
if decoder != nil {
|
||||
return decoder.Decode(value)
|
||||
}
|
||||
// look for Set method if Decode not defined
|
||||
setter := setterFrom(field)
|
||||
if setter != nil {
|
||||
return setter.Set(value)
|
||||
}
|
||||
|
||||
if t := textUnmarshaler(field); t != nil {
|
||||
return t.UnmarshalText([]byte(value))
|
||||
}
|
||||
|
||||
if b := binaryUnmarshaler(field); b != nil {
|
||||
return b.UnmarshalBinary([]byte(value))
|
||||
}
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.New(typ))
|
||||
}
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.String:
|
||||
field.SetString(value)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
var (
|
||||
val int64
|
||||
err error
|
||||
)
|
||||
if field.Kind() == reflect.Int64 && typ.PkgPath() == "time" && typ.Name() == "Duration" {
|
||||
var d time.Duration
|
||||
d, err = time.ParseDuration(value)
|
||||
val = int64(d)
|
||||
} else {
|
||||
val, err = strconv.ParseInt(value, 0, typ.Bits())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.SetInt(val)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
val, err := strconv.ParseUint(value, 0, typ.Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetUint(val)
|
||||
case reflect.Bool:
|
||||
val, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetBool(val)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val, err := strconv.ParseFloat(value, typ.Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetFloat(val)
|
||||
case reflect.Slice:
|
||||
sl := reflect.MakeSlice(typ, 0, 0)
|
||||
if typ.Elem().Kind() == reflect.Uint8 {
|
||||
sl = reflect.ValueOf([]byte(value))
|
||||
} else if len(strings.TrimSpace(value)) != 0 {
|
||||
vals := strings.Split(value, ",")
|
||||
sl = reflect.MakeSlice(typ, len(vals), len(vals))
|
||||
for i, val := range vals {
|
||||
err := processField(val, sl.Index(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
field.Set(sl)
|
||||
case reflect.Map:
|
||||
mp := reflect.MakeMap(typ)
|
||||
if len(strings.TrimSpace(value)) != 0 {
|
||||
pairs := strings.Split(value, ",")
|
||||
for _, pair := range pairs {
|
||||
kvpair := strings.Split(pair, ":")
|
||||
if len(kvpair) != 2 {
|
||||
return fmt.Errorf("invalid map item: %q", pair)
|
||||
}
|
||||
k := reflect.New(typ.Key()).Elem()
|
||||
err := processField(kvpair[0], k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := reflect.New(typ.Elem()).Elem()
|
||||
err = processField(kvpair[1], v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mp.SetMapIndex(k, v)
|
||||
}
|
||||
}
|
||||
field.Set(mp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func interfaceFrom(field reflect.Value, fn func(interface{}, *bool)) {
|
||||
// it may be impossible for a struct field to fail this check
|
||||
if !field.CanInterface() {
|
||||
return
|
||||
}
|
||||
var ok bool
|
||||
fn(field.Interface(), &ok)
|
||||
if !ok && field.CanAddr() {
|
||||
fn(field.Addr().Interface(), &ok)
|
||||
}
|
||||
}
|
||||
|
||||
func decoderFrom(field reflect.Value) (d Decoder) {
|
||||
interfaceFrom(field, func(v interface{}, ok *bool) { d, *ok = v.(Decoder) })
|
||||
return d
|
||||
}
|
||||
|
||||
func setterFrom(field reflect.Value) (s Setter) {
|
||||
interfaceFrom(field, func(v interface{}, ok *bool) { s, *ok = v.(Setter) })
|
||||
return s
|
||||
}
|
||||
|
||||
func textUnmarshaler(field reflect.Value) (t encoding.TextUnmarshaler) {
|
||||
interfaceFrom(field, func(v interface{}, ok *bool) { t, *ok = v.(encoding.TextUnmarshaler) })
|
||||
return t
|
||||
}
|
||||
|
||||
func binaryUnmarshaler(field reflect.Value) (b encoding.BinaryUnmarshaler) {
|
||||
interfaceFrom(field, func(v interface{}, ok *bool) { b, *ok = v.(encoding.BinaryUnmarshaler) })
|
||||
return b
|
||||
}
|
||||
|
||||
func isTrue(s string) bool {
|
||||
b, _ := strconv.ParseBool(s)
|
||||
return b
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
// Copyright (c) 2016 Kelsey Hightower and others. All rights reserved.
|
||||
// Use of this source code is governed by the MIT License that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package envconfig
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultListFormat constant to use to display usage in a list format
|
||||
DefaultListFormat = `This application is configured via the environment. The following environment
|
||||
variables can be used:
|
||||
{{range .}}
|
||||
{{usage_key .}}
|
||||
[description] {{usage_description .}}
|
||||
[type] {{usage_type .}}
|
||||
[default] {{usage_default .}}
|
||||
[required] {{usage_required .}}{{end}}
|
||||
`
|
||||
// DefaultTableFormat constant to use to display usage in a tabular format
|
||||
DefaultTableFormat = `This application is configured via the environment. The following environment
|
||||
variables can be used:
|
||||
|
||||
KEY TYPE DEFAULT REQUIRED DESCRIPTION
|
||||
{{range .}}{{usage_key .}} {{usage_type .}} {{usage_default .}} {{usage_required .}} {{usage_description .}}
|
||||
{{end}}`
|
||||
)
|
||||
|
||||
var (
|
||||
decoderType = reflect.TypeOf((*Decoder)(nil)).Elem()
|
||||
setterType = reflect.TypeOf((*Setter)(nil)).Elem()
|
||||
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
func implementsInterface(t reflect.Type) bool {
|
||||
return t.Implements(decoderType) ||
|
||||
reflect.PtrTo(t).Implements(decoderType) ||
|
||||
t.Implements(setterType) ||
|
||||
reflect.PtrTo(t).Implements(setterType) ||
|
||||
t.Implements(textUnmarshalerType) ||
|
||||
reflect.PtrTo(t).Implements(textUnmarshalerType) ||
|
||||
t.Implements(binaryUnmarshalerType) ||
|
||||
reflect.PtrTo(t).Implements(binaryUnmarshalerType)
|
||||
}
|
||||
|
||||
// toTypeDescription converts Go types into a human readable description
|
||||
func toTypeDescription(t reflect.Type) string {
|
||||
switch t.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if t.Elem().Kind() == reflect.Uint8 {
|
||||
return "String"
|
||||
}
|
||||
return fmt.Sprintf("Comma-separated list of %s", toTypeDescription(t.Elem()))
|
||||
case reflect.Map:
|
||||
return fmt.Sprintf(
|
||||
"Comma-separated list of %s:%s pairs",
|
||||
toTypeDescription(t.Key()),
|
||||
toTypeDescription(t.Elem()),
|
||||
)
|
||||
case reflect.Ptr:
|
||||
return toTypeDescription(t.Elem())
|
||||
case reflect.Struct:
|
||||
if implementsInterface(t) && t.Name() != "" {
|
||||
return t.Name()
|
||||
}
|
||||
return ""
|
||||
case reflect.String:
|
||||
name := t.Name()
|
||||
if name != "" && name != "string" {
|
||||
return name
|
||||
}
|
||||
return "String"
|
||||
case reflect.Bool:
|
||||
name := t.Name()
|
||||
if name != "" && name != "bool" {
|
||||
return name
|
||||
}
|
||||
return "True or False"
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
name := t.Name()
|
||||
if name != "" && !strings.HasPrefix(name, "int") {
|
||||
return name
|
||||
}
|
||||
return "Integer"
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
name := t.Name()
|
||||
if name != "" && !strings.HasPrefix(name, "uint") {
|
||||
return name
|
||||
}
|
||||
return "Unsigned Integer"
|
||||
case reflect.Float32, reflect.Float64:
|
||||
name := t.Name()
|
||||
if name != "" && !strings.HasPrefix(name, "float") {
|
||||
return name
|
||||
}
|
||||
return "Float"
|
||||
}
|
||||
return fmt.Sprintf("%+v", t)
|
||||
}
|
||||
|
||||
// Usage writes usage information to stdout using the default header and table format
|
||||
func Usage(prefix string, spec interface{}) error {
|
||||
// The default is to output the usage information as a table
|
||||
// Create tabwriter instance to support table output
|
||||
tabs := tabwriter.NewWriter(os.Stdout, 1, 0, 4, ' ', 0)
|
||||
|
||||
err := Usagef(prefix, spec, tabs, DefaultTableFormat)
|
||||
tabs.Flush()
|
||||
return err
|
||||
}
|
||||
|
||||
// Usagef writes usage information to the specified io.Writer using the specifed template specification
|
||||
func Usagef(prefix string, spec interface{}, out io.Writer, format string) error {
|
||||
|
||||
// Specify the default usage template functions
|
||||
functions := template.FuncMap{
|
||||
"usage_key": func(v varInfo) string { return v.Key },
|
||||
"usage_description": func(v varInfo) string { return v.Tags.Get("desc") },
|
||||
"usage_type": func(v varInfo) string { return toTypeDescription(v.Field.Type()) },
|
||||
"usage_default": func(v varInfo) string { return v.Tags.Get("default") },
|
||||
"usage_required": func(v varInfo) (string, error) {
|
||||
req := v.Tags.Get("required")
|
||||
if req != "" {
|
||||
reqB, err := strconv.ParseBool(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if reqB {
|
||||
req = "true"
|
||||
}
|
||||
}
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
|
||||
tmpl, err := template.New("envconfig").Funcs(functions).Parse(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Usaget(prefix, spec, out, tmpl)
|
||||
}
|
||||
|
||||
// Usaget writes usage information to the specified io.Writer using the specified template
|
||||
func Usaget(prefix string, spec interface{}, out io.Writer, tmpl *template.Template) error {
|
||||
// gather first
|
||||
infos, err := gatherInfo(prefix, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tmpl.Execute(out, infos)
|
||||
}
|
Loading…
Reference in New Issue