use crate::diagnostics::{DiagnosticDefinition, DiagnosticMap};
#[cfg(test)]
use crate::state_accessors::StateAccessors;
use std::collections::HashMap;
pub const DIAG_INVALID_CONTENT_TYPE_TARGET: &str = "invalid-content-type-target";
pub const DIAG_MULTIPLE_EVENT_PAYLOADS: &str = "multiple-event-payloads";
pub const STATE_EVENTS: &str = "TypeSpec.Events.events";
pub const STATE_CONTENT_TYPE: &str = "TypeSpec.Events.contentType";
pub const STATE_DATA: &str = "TypeSpec.Events.data";
pub const EVENTS_NAMESPACE: &str = "TypeSpec.Events";
#[derive(Debug, Clone)]
pub struct EventDefinition {
pub event_type: Option<String>,
pub envelope_content_type: Option<String>,
pub payload_type: Option<String>,
pub payload_content_type: Option<String>,
pub path_to_payload: String,
}
pub fn create_events_library() -> DiagnosticMap {
HashMap::from([
(
DIAG_INVALID_CONTENT_TYPE_TARGET.to_string(),
DiagnosticDefinition::error(
"@contentType can only be specified on the top-level event envelope, or the event payload marked with @data",
),
),
(
DIAG_MULTIPLE_EVENT_PAYLOADS.to_string(),
DiagnosticDefinition::error_with_messages(vec![
(
"default",
"Event payload already applied to {dataPath} but also exists under {currentPath}",
),
(
"payloadInIndexedModel",
"Event payload applied from inside a Record or Array at {dataPath}",
),
]),
),
])
}
flag_decorator!(apply_events, is_events, STATE_EVENTS);
string_decorator!(apply_content_type, get_content_type, STATE_CONTENT_TYPE);
flag_decorator!(apply_data, is_event_data, STATE_DATA);
pub const EVENTS_DECORATORS_TSP: &str = r#"
using TypeSpec.Reflection;
namespace TypeSpec.Events;
/**
* Specify that this union describes a set of events.
*
* @example
*
* ```typespec
* @events
* union MixedEvents {
* pingEvent: string;
*
* doneEvent: "done";
* }
* ```
*/
extern dec events(target: Union);
/**
* Specifies the content type of the event envelope, event body, or event payload.
* When applied to an event payload, that field must also have a corresponding `@data`
* decorator.
*
* @example
*
* ```typespec
* @events union MixedEvents {
* @contentType("application/json")
* message: { id: string, text: string, }
* }
* ```
*
* @example Specify the content type of the event payload.
*
* ```typespec
* @events union MixedEvents {
* { done: true },
*
* { done: false, @data @contentType("text/plain") value: string,}
* }
* ```
*/
extern dec contentType(target: UnionVariant | ModelProperty, contentType: valueof string);
/**
* Identifies the payload of an event.
* Only one field in an event can be marked as the payload.
*
* @example
*
* ```typespec
* @events union MixedEvents {
* { metadata: Record<string>, @data payload: string,}
* }
* ```
*/
extern dec data(target: ModelProperty);
"#;
pub const EVENTS_MAIN_TSP: &str = r#"
import "../dist/src/tsp-index.js";
import "./decorators.tsp";
"#;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_events_namespace() {
assert_eq!(EVENTS_NAMESPACE, "TypeSpec.Events");
}
#[test]
fn test_state_keys() {
assert!(STATE_EVENTS.starts_with("TypeSpec.Events"));
assert!(STATE_CONTENT_TYPE.starts_with("TypeSpec.Events"));
assert!(STATE_DATA.starts_with("TypeSpec.Events"));
}
#[test]
fn test_create_events_library() {
let diags = create_events_library();
assert_eq!(diags.len(), 2);
let codes: Vec<&str> = diags.keys().map(|code| code.as_str()).collect();
assert!(codes.contains(&DIAG_INVALID_CONTENT_TYPE_TARGET));
assert!(codes.contains(&DIAG_MULTIPLE_EVENT_PAYLOADS));
}
#[test]
fn test_decorators_tsp_not_empty() {
assert!(!EVENTS_DECORATORS_TSP.is_empty());
assert!(EVENTS_DECORATORS_TSP.contains("events"));
assert!(EVENTS_DECORATORS_TSP.contains("contentType"));
assert!(EVENTS_DECORATORS_TSP.contains("data"));
}
#[test]
fn test_event_definition() {
let def = EventDefinition {
event_type: Some("pingEvent".to_string()),
envelope_content_type: Some("application/json".to_string()),
payload_type: Some("string".to_string()),
payload_content_type: None,
path_to_payload: "payload".to_string(),
};
assert_eq!(def.event_type, Some("pingEvent".to_string()));
assert!(def.payload_content_type.is_none());
}
#[test]
fn test_is_events() {
let mut state = StateAccessors::new();
assert!(!is_events(&state, 1));
apply_events(&mut state, 1);
assert!(is_events(&state, 1));
assert!(!is_events(&state, 2));
}
#[test]
fn test_get_content_type() {
let mut state = StateAccessors::new();
assert_eq!(get_content_type(&state, 1), None);
apply_content_type(&mut state, 1, "application/json");
assert_eq!(
get_content_type(&state, 1),
Some("application/json".to_string())
);
}
#[test]
fn test_is_event_data() {
let mut state = StateAccessors::new();
assert!(!is_event_data(&state, 1));
apply_data(&mut state, 1);
assert!(is_event_data(&state, 1));
assert!(!is_event_data(&state, 2));
}
#[test]
fn test_content_type_overwrite() {
let mut state = StateAccessors::new();
apply_content_type(&mut state, 1, "text/plain");
assert_eq!(get_content_type(&state, 1), Some("text/plain".to_string()));
apply_content_type(&mut state, 1, "application/json");
assert_eq!(
get_content_type(&state, 1),
Some("application/json".to_string())
);
}
}