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}