Skip to main content

telltale_runtime/
lib.rs

1//! Choreographic Programming for Telltale
2//!
3//! This crate provides a choreographic programming layer on top of Telltale's
4//! session types, enabling global protocol specification with automatic projection.
5//!
6//! The choreographic approach allows you to write distributed protocols from a
7//! global viewpoint, with automatic generation of local session types for each
8//! participant. This includes an effect handler system that decouples protocol
9//! logic from transport implementation.
10//!
11//! For the current formal-verification claim, only the shipped first-party
12//! handler/transport implementations with documented contract profiles are
13//! inside the first-party runtime boundary. User-supplied third-party handlers
14//! and transports remain outside that claim unless they separately satisfy the
15//! same contract.
16
17#![allow(
18    clippy::missing_errors_doc,
19    clippy::missing_panics_doc,
20    clippy::must_use_candidate
21)]
22
23pub mod ast;
24pub mod compiler;
25pub mod effects;
26pub mod extensions;
27pub mod heap;
28pub mod identifiers;
29pub mod runtime;
30pub mod testing;
31pub mod topology;
32pub mod tracing;
33
34// Re-export runtime support types
35pub use runtime::{SystemClock, SystemRng};
36
37// Re-export typed identifiers
38pub use identifiers::{Endpoint as TopologyEndpoint, Namespace, Region, RoleName};
39
40// Re-export main APIs
41pub use ast::{Choreography, MessageType, Protocol, Role};
42pub use compiler::generate_effects_protocol;
43pub use compiler::{
44    create_standard_extension_parser, format_choreography, format_choreography_str,
45    format_choreography_with_config, ExtensionParseError, ExtensionParser, ExtensionParserBuilder,
46    GrammarComposer, GrammarComposerBuilder, GrammarCompositionError, PrettyConfig,
47};
48pub use effects::middleware::{Metrics, Retry, Trace};
49pub use effects::NoOpHandler;
50pub use effects::{
51    interpret, validate_handler_contract_profile, validated_contract_profile, ChoreoHandler,
52    ChoreoHandlerExt, ChoreoResult, ChoreographyError, DeliveryModel, DocumentedHandlerContract,
53    Effect, Endpoint, ExtensionDispatchContract, ExtensionDispatchMode, HandlerContractProfile,
54    HandlerContractTier, HandlerContractViolation, InterpretResult, InterpreterState, LabelId,
55    MessageTag, Program, ProgramBuilder, ProgramMessage, ProtocolSemanticContract, RetryPolicy,
56    RoleId, TimeoutPolicy, TransportPolicyContract,
57};
58pub use effects::{InMemoryHandler, RecordedEvent, RecordingHandler};
59pub use effects::{TelltaleEndpoint, TelltaleHandler, TelltaleSession};
60pub use extensions::{
61    CodegenContext, ExtensionRegistry, ExtensionValidationError, GrammarExtension, ParseContext,
62    ParseError, ProjectionContext, ProtocolExtension, StatementParser,
63};
64pub use runtime::{spawn, spawn_local};
65pub use topology::{
66    parse_topology, validate_transport_contract_profile, validated_transport_contract_profile,
67    ByteMessage, DocumentedTransportContract, InMemoryChannelTransport, Location, ParsedTopology,
68    RoleFamilyConstraint, RoleFamilyConstraintError, Topology, TopologyBuilder, TopologyConstraint,
69    TopologyError, TopologyHandler, TopologyHandlerBuilder, TopologyLoadError, TopologyMode,
70    TopologyParseError, TopologyValidation, Transport, TransportContractProfile,
71    TransportContractTier, TransportContractViolation, TransportError, TransportFactory,
72    TransportMessage, TransportOperationalContract, TransportResult, TransportSemanticContract,
73    TransportStartupMode, TransportType,
74};
75
76// Re-export heap types for resource management
77pub use heap::{
78    ChannelState, Direction, Heap, HeapCommitment, HeapError, MerkleProof, MerkleTree,
79    Message as HeapMessage, MessagePayload, ProofStep, Resource, ResourceId,
80};
81
82// Re-export testing types for protocol testing
83pub use testing::{
84    AsyncClock, BlockedOn, Checkpoint, Clock, InMemoryTransport, MockClock, NullObserver,
85    ProtocolEnvelope, ProtocolObserver, ProtocolStateMachine, RecordingObserver, Rng, SeededRng,
86    SimulatedTransport, StepInput, StepOutput, WallClock,
87};
88
89// Re-export macros from telltale-macros
90pub use telltale_macros::tell;
91pub use telltale_types::{ChannelCapacity, MessageLenBytes, QueueCapacity};
92
93/// Unstable low-level extension integration surfaces.
94///
95/// These items are exposed for advanced integrations and may evolve faster than
96/// the stable root-level API.
97#[doc(hidden)]
98pub mod unstable {
99    pub use crate::extensions::{CodegenContext, ParseContext, ProjectionContext, StatementParser};
100}
101
102// High-level API functions for extension-aware compilation
103
104/// Parse and generate choreography code with extension support
105pub fn parse_and_generate_with_extensions(
106    input: &str,
107    extension_registry: &ExtensionRegistry,
108) -> std::result::Result<proc_macro2::TokenStream, CompilationError> {
109    use compiler::codegen::generate_choreography_code_with_extensions;
110    use compiler::parser::parse_choreography_str_with_extensions;
111    use compiler::projection::project;
112
113    let (choreography, extensions) =
114        parse_choreography_str_with_extensions(input, extension_registry)
115            .map_err(CompilationError::Parse)?;
116
117    // Validate the choreography
118    choreography
119        .validate()
120        .map_err(|e| CompilationError::Validation(e.to_string()))?;
121
122    // Project to local types
123    let mut local_types = Vec::new();
124    for role in &choreography.roles {
125        let local_type = project(&choreography, role)
126            .map_err(|e| CompilationError::Projection(e.to_string()))?;
127        local_types.push((role.clone(), local_type));
128    }
129
130    // Generate code with extensions
131    let generated_code =
132        generate_choreography_code_with_extensions(&choreography, &local_types, &extensions);
133
134    Ok(generated_code)
135}
136
137/// Convenience function for compiling choreography with built-in extensions
138pub fn compile_choreography_with_extensions(
139    input: &str,
140) -> std::result::Result<proc_macro2::TokenStream, CompilationError> {
141    let registry = ExtensionRegistry::with_builtin_extensions();
142    parse_and_generate_with_extensions(input, &registry)
143}
144
145/// Parse choreography with extension support
146pub fn parse_choreography_with_extensions(
147    input: &str,
148    extension_registry: &ExtensionRegistry,
149) -> std::result::Result<(Choreography, Vec<Box<dyn ProtocolExtension>>), CompilationError> {
150    use compiler::parser::parse_choreography_str_with_extensions;
151
152    parse_choreography_str_with_extensions(input, extension_registry)
153        .map_err(CompilationError::Parse)
154}
155
156/// Compilation errors that can occur during choreography processing
157#[derive(Debug, thiserror::Error)]
158pub enum CompilationError {
159    #[error("parse error: {0}")]
160    Parse(#[from] compiler::parser::ParseError),
161
162    #[error("validation error: {0}")]
163    Validation(String),
164
165    #[error("projection error: {0}")]
166    Projection(String),
167
168    #[error("code generation error: {0}")]
169    Codegen(String),
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use crate::effects::{LabelId, RoleId};
176    use crate::identifiers::RoleName;
177
178    // Simple test role type for unit tests
179    #[allow(dead_code)]
180    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
181    enum TestRole {
182        Alice,
183        Bob,
184    }
185
186    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
187    enum TestLabel {
188        Test,
189    }
190
191    impl LabelId for TestLabel {
192        fn as_str(&self) -> &'static str {
193            match self {
194                TestLabel::Test => "test",
195            }
196        }
197
198        fn from_str(label: &str) -> Option<Self> {
199            match label {
200                "test" => Some(TestLabel::Test),
201                _ => None,
202            }
203        }
204    }
205
206    impl RoleId for TestRole {
207        type Label = TestLabel;
208
209        fn role_name(&self) -> RoleName {
210            match self {
211                TestRole::Alice => RoleName::from_static("Alice"),
212                TestRole::Bob => RoleName::from_static("Bob"),
213            }
214        }
215    }
216
217    #[test]
218    fn test_module_structure() {
219        // Test that main re-exports are available
220        let _choreography: Option<Choreography> = None;
221        let _protocol: Option<Protocol> = None;
222        let _role: Option<Role> = None;
223        let _message_type: Option<MessageType> = None;
224
225        // Test effect system is available
226        let _program: Option<Program<TestRole, String>> = None;
227        let _result: Option<ChoreoResult<()>> = None;
228        let _label: Option<TestLabel> = None;
229    }
230
231    #[test]
232    fn test_free_algebra_integration() {
233        use std::time::Duration;
234
235        // Test that Program can be built using the free algebra API
236        let program = Program::<TestRole, String>::new()
237            .send(TestRole::Bob, "hello".to_string())
238            .recv::<String>(TestRole::Bob)
239            .choose(TestRole::Bob, TestLabel::Test)
240            .offer(TestRole::Bob)
241            .with_timeout(
242                TestRole::Bob,
243                Duration::from_millis(100),
244                Program::new().end(),
245            )
246            .parallel(vec![Program::new().end()])
247            .end();
248
249        // Basic analysis should work
250        assert_eq!(program.send_count(), 1);
251        assert_eq!(program.recv_count(), 1);
252        assert!(program.has_timeouts());
253        assert!(program.has_parallel());
254    }
255}