Skip to main content

nakadi_types/event_type/
mod.rs

1//! Types for defining and monitoring event types
2use std::time::Duration;
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7use crate::misc::{AuthorizationAttribute, AuthorizationAttributes, OwningApplication};
8
9mod event_type_input;
10pub use event_type_input::*;
11
12new_type! {
13    #[doc=r#"Name of an EventType. The name is constrained by a regular expression.
14
15Note: the name can encode the owner/responsible for this EventType and ideally should
16follow a common pattern that makes it easy to read and understand, but this level of
17structure is not enforced. For example a team name and data type can be used such as
18‘acme-team.price-change’.
19
20See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventType*name)"#]
21    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
22    pub struct EventTypeName(String, env="EVENT_TYPE_NAME");
23}
24
25/// Defines the category of this EventType.
26///
27/// The value set will influence, if not set otherwise, the default set of
28/// validations, enrichment-strategies, and the effective schema for validation.
29///
30/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventType*category)
31#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum Category {
34    /// No predefined changes apply. The effective schema for the validation is
35    /// exactly the same as the EventTypeSchema.
36    Undefined,
37    /// Events of this category will be DataChangeEvents. The effective schema during
38    /// the validation contains metadata, and adds fields data_op and data_type. The
39    /// passed EventTypeSchema defines the schema of data.
40    Data,
41    /// Events of this category will be BusinessEvents. The effective schema for
42    /// validation contains metadata and any additionally defined properties passed in the
43    /// EventTypeSchema directly on top level of the Event. If name conflicts arise, creation
44    /// of this EventType will be rejected.
45    Business,
46}
47
48/// Determines how the assignment of the event to a partition should be handled.
49///
50/// The default is `random`.
51///
52/// See also [Nakadi Manual](https://nakadi.io/manual.html#/registry/partition-strategies_get)
53#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
54#[serde(rename_all = "snake_case")]
55pub enum PartitionStrategy {
56    /// Resolution of the target partition happens randomly (events are evenly
57    /// distributed on the topic’s partitions).
58    Random,
59    /// Resolution of the partition follows the computation of a hash from the value of
60    /// the fields indicated in the EventType’s partition_key_fields, guaranteeing that Events
61    /// with same values on those fields end in the same partition. Given the event type’s category
62    /// is DataChangeEvent, field path is considered relative to “data”.
63    Hash,
64    /// Target partition is defined by the client. As long as the indicated
65    /// partition exists, Event assignment will respect this value. Correctness of the relative
66    /// ordering of events is under the responsibility of the Producer. Requires that the client
67    /// provides the target partition on metadata.partition (See EventMetadata). Failure to do
68    /// so will reject the publishing of the Event.
69    UserDefined,
70}
71
72impl Default for PartitionStrategy {
73    fn default() -> Self {
74        PartitionStrategy::Random
75    }
76}
77
78/// Compatibility mode provides a mean for event owners to evolve their schema, given changes respect the
79/// semantics defined by this field.
80///
81/// It’s designed to be flexible enough so that producers can evolve their schemas while not
82/// inadvertently breaking existent consumers.
83///
84/// Once defined, the compatibility mode is fixed, since otherwise it would break a predefined contract,
85/// declared by the producer.
86///
87/// The default is `forward`.
88///
89/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventType*compatibility_mode)
90#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum CompatibilityMode {
93    /// Consumers can reliably parse events produced under different versions. Every event published
94    /// since the first version is still valid based on the newest schema. When in compatible mode, it’s allowed to
95    /// add new optional properties and definitions to an existing schema, but no other changes are allowed.
96    /// Under this mode, the following json-schema attributes are not supported: `not`, `patternProperties`,
97    /// `additionalProperties` and `additionalItems`. When validating events, additional properties is `false`.
98    Compatible,
99    /// Compatible schema changes are allowed. It’s possible to use the full json schema specification
100    /// for defining schemas. Consumers of forward compatible event types can safely read events tagged with the
101    /// latest schema version as long as they follow the robustness principle.
102    Forward,
103    /// Any schema modification is accepted, even if it might break existing producers or consumers. When
104    /// validating events, no additional properties are accepted unless explicitly stated in the schema.
105    None,
106}
107
108impl Default for CompatibilityMode {
109    fn default() -> Self {
110        CompatibilityMode::Forward
111    }
112}
113
114new_type! {
115#[doc="Part of `PartitionKeyFields`\n"]
116#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
117    pub struct PartitionKeyField(String, env="EVENT_TYPE_PARTITION_KEY_FIELD");
118}
119
120/// Required when 'partition_resolution_strategy' is set to ‘hash’. Must be absent otherwise.
121/// Indicates the fields used for evaluation the partition of Events of this type.
122///
123/// If this is set it MUST be a valid required field as defined in the schema.
124///
125/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventType*partition_key_fields)
126#[derive(Debug, Default, Clone, Serialize, Deserialize)]
127pub struct PartitionKeyFields(Vec<PartitionKeyField>);
128
129impl PartitionKeyFields {
130    pub fn new<I>(items: I) -> Self
131    where
132        I: IntoIterator,
133        I::Item: Into<PartitionKeyField>,
134    {
135        let items = items.into_iter().map(|it| it.into()).collect();
136        Self(items)
137    }
138
139    pub fn partition_key<T: Into<PartitionKeyField>>(mut self, v: T) -> Self {
140        self.push(v);
141        self
142    }
143
144    pub fn push<T: Into<PartitionKeyField>>(&mut self, v: T) {
145        self.0.push(v.into());
146    }
147
148    pub fn into_inner(self) -> Vec<PartitionKeyField> {
149        self.0
150    }
151
152    pub fn iter(&self) -> impl Iterator<Item = &PartitionKeyField> {
153        self.0.iter()
154    }
155
156    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut PartitionKeyField> {
157        self.0.iter_mut()
158    }
159
160    pub fn is_empty(&self) -> bool {
161        self.0.is_empty()
162    }
163
164    pub fn len(&self) -> usize {
165        self.0.len()
166    }
167}
168
169impl<A> From<A> for PartitionKeyFields
170where
171    A: Into<PartitionKeyField>,
172{
173    fn from(k: A) -> Self {
174        Self(vec![k.into()])
175    }
176}
177
178impl<A, B> From<(A, B)> for PartitionKeyFields
179where
180    A: Into<PartitionKeyField>,
181    B: Into<PartitionKeyField>,
182{
183    fn from((a, b): (A, B)) -> Self {
184        Self(vec![a.into(), b.into()])
185    }
186}
187
188impl<A, B, C> From<(A, B, C)> for PartitionKeyFields
189where
190    A: Into<PartitionKeyField>,
191    B: Into<PartitionKeyField>,
192    C: Into<PartitionKeyField>,
193{
194    fn from((a, b, c): (A, B, C)) -> Self {
195        Self(vec![a.into(), b.into(), c.into()])
196    }
197}
198
199impl AsRef<[PartitionKeyField]> for PartitionKeyFields {
200    fn as_ref(&self) -> &[PartitionKeyField] {
201        &self.0
202    }
203}
204
205/// Event type cleanup policy. There are two possible values.
206///
207/// It’s not possible to change the value of this field for existing event type.
208///
209/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventType*cleanup_policy)
210#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
211#[serde(rename_all = "snake_case")]
212pub enum CleanupPolicy {
213    /// This cleanup policy will delete old events after retention time expires. Nakadi guarantees that each
214    /// event will be available for at least the retention time period. However Nakadi doesn’t guarantee that event
215    /// will be deleted right after retention time expires.
216    Delete,
217    /// This cleanup policy will keep only the latest event for each event key. The compaction is performed per
218    /// partition, there is no compaction across partitions. The key that will be used as a compaction key should be
219    /// specified in ‘partition_compaction_key’ field of event metadata. This cleanup policy is not available for
220    /// ‘undefined’ category of event types.
221    ///
222    /// The compaction can be not applied to events that were published recently and located at the head of the
223    /// queue, which means that the actual amount of events received by consumers can be different depending on time
224    /// when the consumption happened.
225    ///
226    /// When using ‘compact’ cleanup policy user should consider that different Nakadi endpoints showing the amount
227    /// of events will actually show the original amount of events published, not the actual amount of events that
228    /// are currently there.
229    /// E.g. subscription /stats endpoint will show the value ‘unconsumed_events’ - but that may not match with the
230    /// actual amount of events unconsumed in that subscription as ‘compact’ cleanup policy may delete older events
231    /// in the middle of queue if there is a newer event for the same key published.
232    ///
233    /// For more details about compaction implementation please read the documentation of Log Compaction in
234    /// [Kafka](https://kafka.apache.org/documentation/#compaction), Nakadi currently relies on this implementation.
235    Compact,
236}
237
238impl Default for CleanupPolicy {
239    fn default() -> Self {
240        CleanupPolicy::Delete
241    }
242}
243
244/// The type of schema definition. Currently only json_schema (JSON Schema v04) is supported, but in the
245/// future there could be others.
246#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
247#[non_exhaustive]
248#[serde(rename_all = "snake_case")]
249pub enum SchemaType {
250    #[serde(rename = "json_schema")]
251    JsonSchema,
252}
253
254/// The most recent schema for this EventType. Submitted events will be validated against it.
255#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
256pub struct EventTypeSchema {
257    /// This field is automatically generated by Nakadi. Values are based on semantic versioning. Changes to title
258    /// or description are considered PATCH level changes. Adding new optional fields is considered a MINOR level
259    /// change. All other changes are considered MAJOR level.
260    pub version: String,
261    /// Creation timestamp of the schema. This is generated by Nakadi. It should not be
262    /// specified when updating a schema and sending it may result in a client error.
263    pub created_at: DateTime<Utc>,
264    ///The type of schema definition. Currently only json_schema (JSON Schema v04) is supported, but in the
265    ///future there could be others.
266    #[serde(rename = "type")]
267    pub schema_type: SchemaType,
268    /// The schema as string in the syntax defined in the field type. Failure to respect the
269    /// syntax will fail any operation on an EventType.
270    pub schema: SchemaSyntax,
271}
272
273new_type! {
274#[doc=r#"
275The schema as string in the syntax defined in the field type.
276
277Failure to respect the
278syntax will fail any operation on an EventType.
279
280"#]
281#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
282    pub struct SchemaSyntax(String, env="EVENT_TYPE_SCHEMA_SYNTAX");
283}
284
285new_type! {
286    #[doc="Number of milliseconds that Nakadi stores events published to this event type.\n\n\
287    See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventTypeOptions*retention_time)"]
288    #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
289    pub copy struct RetentionTime(u64);
290}
291
292impl RetentionTime {
293    pub fn to_duration(self) -> Duration {
294        Duration::from_millis(self.0)
295    }
296}
297
298/// Additional parameters for tuning internal behavior of Nakadi.
299///
300/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventTypeOptions)
301#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize)]
302pub struct EventTypeOptions {
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub retention_time: Option<RetentionTime>,
305}
306
307#[derive(Debug, Default, Clone, Serialize, Deserialize)]
308pub struct EventTypeAuthorization {
309    #[serde(default)]
310    pub admins: AuthorizationAttributes,
311    #[serde(default)]
312    pub readers: AuthorizationAttributes,
313    #[serde(default)]
314    pub writers: AuthorizationAttributes,
315}
316
317impl EventTypeAuthorization {
318    pub fn new<A, R, W>(admins: A, readers: R, writers: W) -> Self
319    where
320        A: Into<AuthorizationAttributes>,
321        R: Into<AuthorizationAttributes>,
322        W: Into<AuthorizationAttributes>,
323    {
324        Self {
325            admins: admins.into(),
326            readers: readers.into(),
327            writers: writers.into(),
328        }
329    }
330
331    pub fn admin<T: Into<AuthorizationAttribute>>(mut self, admin: T) -> Self {
332        self.admins.push(admin.into());
333        self
334    }
335    pub fn reader<T: Into<AuthorizationAttribute>>(mut self, reader: T) -> Self {
336        self.readers.push(reader.into());
337        self
338    }
339    pub fn writer<T: Into<AuthorizationAttribute>>(mut self, writer: T) -> Self {
340        self.writers.push(writer.into());
341        self
342    }
343
344    pub fn add_admin<T: Into<AuthorizationAttribute>>(&mut self, admin: T) {
345        self.admins.push(admin.into())
346    }
347    pub fn add_reader<T: Into<AuthorizationAttribute>>(&mut self, reader: T) {
348        self.readers.push(reader.into())
349    }
350    pub fn add_writer<T: Into<AuthorizationAttribute>>(&mut self, writer: T) {
351        self.writers.push(writer.into())
352    }
353}
354
355/// Intended target audience of the event type. Relevant for standards around quality of design and documentation,
356/// reviews, discoverability, changeability, and permission granting. See the guidelines
357/// https://opensource.zalando.com/restful-api-guidelines/#219
358///
359/// This attribute adds no functionality and is used only to inform users about the usage scope of the event type.
360///
361/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventType*audience)
362#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
363pub enum EventTypeAudience {
364    #[serde(rename = "component-internal")]
365    ComponentInternal,
366    #[serde(rename = "business-unit-internal")]
367    BusinessUnitInternal,
368    #[serde(rename = "company-internal")]
369    CompanyInternal,
370    #[serde(rename = "external-partner")]
371    ExternalPartner,
372    #[serde(rename = "external-public")]
373    ExternalPublic,
374}
375
376/// Determines the enrichment to be performed on an Event upon reception. Enrichment is
377/// performed once upon reception (and after validation) of an Event and is only possible on
378/// fields that are not defined on the incoming Event.
379///
380/// For event types in categories `business` or `data` it’s mandatory to use
381/// metadata_enrichment strategy. For `undefined` event types it’s not possible to use this
382/// strategy, since metadata field is not required.
383///
384/// See documentation for the write operation for details on behaviour in case of unsuccessful
385/// enrichment.
386///
387/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventType*enrichment_strategies)
388#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
389#[non_exhaustive]
390#[serde(rename_all = "snake_case")]
391pub enum EnrichmentStrategy {
392    MetadataEnrichment,
393}
394
395impl Default for EnrichmentStrategy {
396    fn default() -> Self {
397        EnrichmentStrategy::MetadataEnrichment
398    }
399}
400
401/// Operational statistics for an EventType. This data may be provided by users on Event Type creation.
402/// Nakadi uses this object in order to provide an optimal number of partitions from a throughput perspective.
403///
404/// This field defines the number of partitions in the underlying Kafka topic of an event type.
405/// The amount of partitions is given by the expression max(read_parallelism, write_parallelism).
406/// The maximum number of partitions is specific to each deployment of Nakadi
407/// and should be referred to in a separated document.
408///
409/// For historical reasons the way that the number of partitions is defined is not as straighforward as it could.
410/// The fields messages_per_minute and message_size could potentially influence the resulting amount of partitions,
411/// so it’s recommended to set both of them to 1 (one).
412/// Providing values different than 1 could result in a higher number of partitions being created.
413///
414/// For those interested in why these fields exist, in the beginning of the project the developers
415/// run a very rudimentary benchmark to understand how much data could be ingested by a single Kafka topic-partition.
416/// This benchmark data was later used by this feature to define the suposedely
417/// ideal number of partitions for the user’s needs. Over time the maintainers of
418/// the project found this benchmark to be unreliable,
419/// usually resulting in fewer partitions than needed.
420///  
421/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventTypeStatistics)
422#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
423pub struct EventTypeStatistics {
424    /// Write rate for events of this EventType. This rate encompasses all producers of this
425    /// EventType for a Nakadi cluster.
426    ///
427    /// Measured in event count per minute.
428    pub messages_per_minute: u64,
429    /// Average message size for each Event of this EventType. Includes in the count the whole serialized
430    /// form of the event, including metadata.
431    /// Measured in bytes.
432    pub message_size: u64,
433    /// Amount of parallel readers (consumers) to this EventType.
434    pub read_parallelism: u64,
435    /// Amount of parallel writers (producers) to this EventType.
436    pub write_parallelism: u64,
437}
438
439impl EventTypeStatistics {
440    pub fn new(
441        messages_per_minute: u64,
442        message_size: u64,
443        read_parallelism: u64,
444        write_parallelism: u64,
445    ) -> Self {
446        Self {
447            messages_per_minute,
448            message_size,
449            read_parallelism,
450            write_parallelism,
451        }
452    }
453}
454
455/// Definition of an event type
456///
457/// This struct is only used for querying from Nakadi.
458///
459/// See also [Nakadi Manual](https://nakadi.io/manual.html#definition_EventType)
460#[derive(Debug, Clone, Serialize, Deserialize)]
461pub struct EventType {
462    /// Name of this EventType. The name is constrained by a regular expression.
463    ///
464    /// Note: the name can encode the owner/responsible for this EventType and ideally should
465    /// follow a common pattern that makes it easy to read and understand, but this level of
466    /// structure is not enforced. For example a team name and data type can be used such as
467    /// ‘acme-team.price-change’.
468    pub name: EventTypeName,
469    /// Indicator of the application owning this EventType.
470    pub owning_application: Option<OwningApplication>,
471    /// Defines the category of this EventType.
472    ///
473    /// The value set will influence, if not set otherwise, the default set of
474    /// validations, enrichment-strategies, and the effective schema for validation.
475    pub category: Category,
476    /// Determines the enrichment to be performed on an Event upon reception. Enrichment is
477    /// performed once upon reception (and after validation) of an Event and is only possible on
478    /// fields that are not defined on the incoming Event.
479    ///
480    /// For event types in categories ‘business’ or ‘data’ it’s mandatory to use
481    /// metadata_enrichment strategy. For ‘undefined’ event types it’s not possible to use this
482    /// strategy, since metadata field is not required.
483    ///
484    /// See documentation for the write operation for details on behaviour in case of unsuccessful
485    /// enrichment.
486    #[serde(default)]
487    pub enrichment_strategies: Vec<EnrichmentStrategy>,
488    /// Determines how the assignment of the event to a partition should be handled.
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub partition_strategy: Option<PartitionStrategy>,
491    /// Compatibility mode provides a mean for event owners to evolve their schema, given changes respect the
492    /// semantics defined by this field.
493    ///
494    /// It’s designed to be flexible enough so that producers can evolve their schemas while not
495    /// inadvertently breaking existent consumers.
496    ///
497    /// Once defined, the compatibility mode is fixed, since otherwise it would break a predefined contract,
498    /// declared by the producer.
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub compatibility_mode: Option<CompatibilityMode>,
501    pub schema: EventTypeSchema,
502    /// Required when ‘partition_resolution_strategy’ is set to ‘hash’. Must be absent otherwise.
503    /// Indicates the fields used for evaluation the partition of Events of this type.
504    ///
505    /// If this is set it MUST be a valid required field as defined in the schema.
506    #[serde(default)]
507    pub partition_key_fields: PartitionKeyFields,
508    /// Event type cleanup policy. There are two possible values:
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub cleanup_policy: Option<CleanupPolicy>,
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub default_statistic: Option<EventTypeStatistics>,
513    #[serde(default)]
514    pub options: EventTypeOptions,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub authorization: Option<EventTypeAuthorization>,
517    pub audience: Option<EventTypeAudience>,
518    /// This is only an informational field. The events are delivered to consumers in the order they were published.
519    /// No reordering is done by Nakadi.
520    ///
521    /// This field is useful in case the producer wants to communicate the complete order accross all the events
522    /// published to all partitions. This is the case when there is an incremental generator on the producer side,
523    /// for example.
524    ///
525    /// It differs from partition_key_fields in the sense that it’s not used for partitioning (known as sharding in
526    /// some systems). The order indicated by ordering_key_fields can also differ from the order the events are in
527    /// each partition, in case of out-of-order submission.
528    ///
529    /// In most cases, this would have just a single item (the path of the field
530    /// by which this is to be ordered), but can have multiple items, in which case
531    /// those are considered as a compound key, with lexicographic ordering (first
532    /// item is most significant).
533    #[serde(default)]
534    pub ordering_key_fields: Vec<String>,
535    #[serde(default)]
536    pub ordering_instance_ids: Vec<String>,
537    /// Date and time when this event type was created.
538    pub created_at: DateTime<Utc>,
539    /// Date and time when this event type was updated.
540    pub updated_at: DateTime<Utc>,
541}