diff --git a/api/README.md b/api/README.md index e68bb8fc..f8da0a32 100644 --- a/api/README.md +++ b/api/README.md @@ -4,9 +4,11 @@ The base classes, interfaces and low-level APIs to use CloudEvents. ## How to Use +Here we will see how to use the pre-configure marshallers and unmarshallers. + ### Binary Marshaller -The low-level API to marshal CloudEvents as binary content mode. +The high-level API to marshal CloudEvents as binary content mode. ```java import java.net.URI; @@ -14,12 +16,9 @@ import java.time.ZonedDateTime; import io.cloudevents.extensions.DistributedTracingExtension; import io.cloudevents.extensions.ExtensionFormat; -import io.cloudevents.json.Json; -import io.cloudevents.v02.Accessor; -import io.cloudevents.v02.AttributesImpl; +import io.cloudevents.format.Wire; import io.cloudevents.v02.CloudEventBuilder; import io.cloudevents.v02.CloudEventImpl; -import io.cloudevents.v02.http.HeaderMapper; //... @@ -35,28 +34,22 @@ final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt); /* Build a CloudEvent instance */ -final CloudEventImpl ce = - CloudEventBuilder.builder() - .withType("com.github.pull.create") - .withSource(URI.create("https://github.com/cloudevents/spec/pull")) - .withId("A234-1234-1234") - .withSchemaurl(URI.create("http://my.br")) - .withTime(ZonedDateTime.now()) - .withContenttype("text/plain") - .withData("my-data") - .withExtension(tracing) - .build(); +CloudEventImpl ce = + CloudEventBuilder.builder() + .withType("com.github.pull.create") + .withSource(URI.create("https://github.com/cloudevents/spec/pull")) + .withId("A234-1234-1234") + .withSchemaurl(URI.create("http://my.br")) + .withTime(ZonedDateTime.now()) + .withContenttype("text/plain") + .withData("my-data") + .withExtension(tracing) + .build(); -/*Marshal the event as a Wire instance */ -final Wire wire = - BinaryMarshaller. - builder() - .map(AttributesImpl::marshal) - .map(Accessor::extensionsOf) - .map(ExtensionFormat::marshal) - .map(HeaderMapper::map) - .map(Json.marshaller()::marshal) - .builder(Wire::new) +/* Marshal the event as a Wire instance */ +Wire wire = + Marshallers. + binary() .withEvent(() -> ce) .marshal(); @@ -72,7 +65,7 @@ wire.getPayload(); //Optional which has the JSON ### Binary Umarshaller -The low-level API to unmarshal CloudEvents from binary content mode. +The high-level API to unmarshal CloudEvents from binary content mode. ```java import java.util.HashMap; @@ -80,11 +73,9 @@ import java.util.Map; import io.cloudevents.CloudEvent; import io.cloudevents.extensions.DistributedTracingExtension; -import io.cloudevents.json.Json; import io.cloudevents.v02.AttributesImpl; import io.cloudevents.v02.CloudEventBuilder; -import io.cloudevents.v02.http.AttributeMapper; -import io.cloudevents.v02.http.ExtensionMapper; +import io.cloudevents.v02.http.Unmarshallers; // . . . @@ -106,20 +97,10 @@ String myPayload = "{\"foo\" : \"rocks\", \"name\" : \"jocker\"}"; /* Unmarshals as CloudEvent instance */ CloudEvent event = - BinaryUnmarshaller. - builder() - .map(AttributeMapper::map) - .map(AttributesImpl::unmarshal) - .map("application/json", Json.umarshaller(Map.class)::unmarshal) - .map("text/plain", (payload, attributes) -> new HashMap<>()) - .next() - .map(ExtensionMapper::map) - .map(DistributedTracingExtension::unmarshall) - .next() - .builder(CloudEventBuilder.builder()::build) - .withHeaders(() -> httpHeaders) - .withPayload(() -> myPayload) - .unmarshal(); + Unmarshallers.binary(Map.class) + .withHeaders(() -> httpHeaders) + .withPayload(() -> myPayload) + .unmarshal(); /* Use the CloudEvent instance attributes, data and extensions */ event.getAttributes(); @@ -129,21 +110,15 @@ event.getExtensions(); ### Structured Marshaller -The low-level API to marshal CloudEvents as structured content mode. +The high-level API to marshal CloudEvents as structured content mode. ```java import java.net.URI; import java.time.ZonedDateTime; -import java.util.HashMap; import io.cloudevents.extensions.DistributedTracingExtension; import io.cloudevents.extensions.ExtensionFormat; -import io.cloudevents.json.Json; -import io.cloudevents.v02.Accessor; -import io.cloudevents.v02.AttributesImpl; -import io.cloudevents.v02.CloudEventBuilder; -import io.cloudevents.v02.CloudEventImpl; -import io.cloudevents.v02.http.HeaderMapper; +import io.cloudevents.v02.http.Marshallers; // . . . @@ -168,15 +143,7 @@ final CloudEventImpl ce = .build(); final Wire wire = - StructuredMarshaller. - builder() - .mime("Content-Type", "application/cloudevents+json") - .map(event -> { - return Json.marshaller().marshal(event, new HashMap<>()); - }) - .map(Accessor::extensionsOf) - .map(ExtensionFormat::marshal) - .map(HeaderMapper::map) + Marshallers.structured() .withEvent(() -> ce) .marshal(); @@ -184,7 +151,7 @@ final Wire wire = * Use the wire result, getting the headers map * and the actual payload */ -wire.getHeaders(); //Map +wire.getHeaders(); //Map wire.getPayload(); //Optional which has the JSON // Use in the transport binding: http, kafka, etc ... @@ -192,4 +159,251 @@ wire.getPayload(); //Optional which has the JSON ### Structured Unmarshaller -TODO +The high-level API to unmarshal CloudEvents as structured content mode. + +```java +import java.util.HashMap; +import java.util.Map; + +import io.cloudevents.CloudEvent; +import io.cloudevents.v02.AttributesImpl; + +// . . . + +/* The HTTP Headers */ +Map httpHeaders = new HashMap<>(); +httpHeaders.put("Content-Type", "application/cloudevents+json"); + +/* Distributed Tracing */ +httpHeaders.put("traceparent", "0x200"); +httpHeaders.put("tracestate", "congo=9"); + + +/* JSON Payload */ +String payload = "{\"data\":{\"wow\":\"yes!\"},\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.2\",\"type\":\"event-type\",\"contenttype\":\"application/json\"}"; + +/* Unmarshalling . . . */ +CloudEvent event = + Unmarshallers.structured(Map.class) + .withHeaders(() -> httpHeaders) + .withPayload(() -> payload) + .unmarshal(); + +/* Use the event instance */ +event.getAttributes(); +event.getExtensions(); +event.getData(); +``` + +## Low-level (Un)Marshalling + +When you need to process different formats, instead of JSON, or if you have a custom extension implementation. You must develop your own (un)marshallers. + +We provide a way to make it easy, well, or with less pain at all. + +Let us introduce the step builders: + +- [BinaryMarshaller](./src/main/java/io/cloudevents/format/BinaryMarshaller.java) +- [StructuredMarshaller](./src/main/java/io/cloudevents/format/StructuredMarshaller.java) +- [BinaryUnmarshaller](./src/main/java/io/cloudevents/format/BinaryUnmarshaller.java) +- [StructuredUnmarshaller](./src/main/java/io/cloudevents/format/StructuredUnmarshaller.java) + +All of them follow the [step builder pattern](https://java-design-patterns.com/patterns/step-builder/), that helps a lot when you are new in the data (un)marshalling. To support them we have a [bunch of functional interfaces](./src/main/java/io/cloudevents/fun), used by each step. + +### Marshaller + +Well, this is how build marshaller using the low-level API. + +**Binary Marshaller** + +```java +/* + * The imports used by the example bellow + */ +import io.cloudevents.extensions.ExtensionFormat; +import io.cloudevents.format.BinaryMarshaller; +import io.cloudevents.format.Wire; +import io.cloudevents.format.builder.EventStep; +import io.cloudevents.json.Json; +import io.cloudevents.json.types.Much; +import io.cloudevents.v02.Accessor; +import io.cloudevents.v02.AttributesImpl; + +// . . . + +/* + * Step 0. Define the types - there are four + * - Type 1 -> AttributesImpl: the implementation of attributes + * - Type 2 -> Much..........: the type CloudEvents' 'data' + * - Type 3 -> String........: the type of payload that will result of marshalling + * - Type 4 -> String........: the type of headers values. String for HTTP, byte[] for Kafka . . . + */ +EventStep builder = + BinaryMarshaller. + builder() + /* + * Step 1. The attributes marshalling + * - in this step we must provide an impl able to marshal AttributesImpl into a + * Map + */ + .map(AttributesImpl::marshal) + + /* + * Step 2. Access the internal list of extensions + * - here we must provide a accessor of internal list of extensions + */ + .map(Accessor::extensionsOf) + + /** + * Step 3. The extensions marshalling + * - we must provide an impl able to marshal a Collection into a + * Map + */ + .map(ExtensionFormat::marshal) + + /* + * Step 4. Mapping to headers + * - provide an impl able to map from attributes and extensions into a + * Map, the headers of transport binding. + */ + .map(HeaderMapper::map) + + /* + * Step 5. The data marshaller + * - provider an impl able to marshal the CloudEvents' data into payload + * for transport + */ + .map(Json.marshaller()::marshal) + + /* + * Step 6. The wire builder + * - to make easy to get the marshalled paylaod and the headers we + * have the Wire + * - here must to provide a way to create new instance of Wire + * - now we get the EventStep, a common step + * that every marshaller returns + * - from here we just call withEvent() and marshal() methods + */ + .builder(Wire::new); + +/* + * Using the marshaller + */ +Wire wire = + builder + .withEvent(() -> myEvent) + .marshal(); +} +``` + +**Structured Marshaller** + +### Unmarshaller + +**Binary Unmarshaller** + +```java +/* +* The imports used by the example bellow +*/ +import io.cloudevents.CloudEvent; +import io.cloudevents.extensions.DistributedTracingExtension; +import io.cloudevents.format.BinaryUnmarshaller; +import io.cloudevents.format.builder.HeadersStep; +import io.cloudevents.json.Json; +import io.cloudevents.json.types.Much; +import io.cloudevents.v02.AttributesImpl; +import io.cloudevents.v02.CloudEventBuilder; + +// . . . + +/* + * Step 0. Define the types - there are three + * - Type 1 -> AttributesImpl: the implementation of attributes + * - Type 2 -> Much..........: the type CloudEvents' 'data' + * - Type 3 -> String........: the type of payload used in the unmarshalling + */ +HeadersStep builder = + BinaryUnmarshaller. + builder() + /* + * Step 1. Mapping from headers Map to attributes one + * - we must provide a mapper able to map from transport headers to a map of attributes + * - this like a translation, removing the prefixes or unknow names for example + */ + .map(AttributeMapper::map) + + /* + * Step 2. The attributes ummarshalling + * - we must provide an impl able to unmarshal a Map of attributes into + * an instance of Attributes + */ + .map(AttributesImpl::unmarshal) + + /* + * Step 3. The data umarshaller + * - we may provive more than one unmarshaller per media type + * - we must provide an impl able to unmashal the payload into + * the actual 'data' type instance + */ + .map("application/json", Json.umarshaller(Much.class)::unmarshal) + + /* + * Step 3'. Another data unmarshaller + */ + .map("media type", (payload, headers) -> { + + return null; + }) + + /* + * When we are ok with data unmarshallers we call next() + */ + .next() + + /* + * Step 4. The extension mapping + * - we must provider an impl able to map from transport headers to map of extensions + * - we may use this for extensions that lives in the transport headers + */ + .map(ExtensionMapper::map) + + /* + * Step 5. The extension unmarshaller + * - we must provide an impl able to unmarshal from map of extenstions into + * actual ones + */ + .map(DistributedTracingExtension::unmarshall) + + /* + * Step 5'. Another extension unmarshaller + */ + .map((extensionsMap) -> { + + return null; + }) + + /* + * When we are ok with extensions unmarshallers we call next() + */ + .next() + + /* + * Step 6. The CloudEvent builder + * - we must provide an impl able to take the extensions, data and attributes + * and build CloudEvent instances + * - now we get the HeadersStep, a common step + * that event unmarshaller must returns + * - from here we just call withHeaders(), withPayload() and unmarshal() + */ + .builder(CloudEventBuilder.builder()::build); + +/* + * Using the unmarshaller + */ +CloudEvent myEvent = + builder + .withHeaders(() -> transportHeaders) + .withPayload(() -> payload) + .unmarshal(); +```