From 0d548f779868c5d4705f0c18742d5500b2c5f01f Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Wed, 9 Feb 2022 21:32:32 +0100 Subject: [PATCH] working codegen --- amqp_codegen/src/main.rs | 128 ++++++++++- amqp_transport/src/classes/mod.rs | 346 +++++++++++++++++++++++++++++- amqp_transport/src/frame.rs | 2 +- 3 files changed, 463 insertions(+), 13 deletions(-) diff --git a/amqp_codegen/src/main.rs b/amqp_codegen/src/main.rs index 92c4288..f33223a 100644 --- a/amqp_codegen/src/main.rs +++ b/amqp_codegen/src/main.rs @@ -1,7 +1,7 @@ -use anyhow::Result; -use heck::{ToSnakeCase, ToUpperCamelCase}; +use anyhow::{Context, Result}; +use heck::ToUpperCamelCase; use std::fs; -use strong_xml::{XmlError, XmlRead}; +use strong_xml::XmlRead; #[derive(Debug, XmlRead)] #[xml(tag = "amqp")] @@ -28,6 +28,12 @@ struct Domain { struct Assert { #[xml(attr = "check")] check: String, + #[xml(attr = "method")] + method: Option, + #[xml(attr = "field")] + field: Option, + #[xml(attr = "value")] + value: Option, } #[derive(Debug, XmlRead)] @@ -61,6 +67,8 @@ struct Field { name: String, #[xml(attr = "domain")] domain: Option, + #[xml(attr = "type")] + kind: Option, #[xml(child = "assert")] asserts: Vec, } @@ -73,20 +81,52 @@ fn main() -> Result<()> { } fn codegen(amqp: &Amqp) -> Result<()> { + println!("use std::collections::HashMap;\n"); + domain_defs(amqp)?; + class_defs(amqp) +} + +fn domain_defs(amqp: &Amqp) -> Result<()> { + for domain in &amqp.domains { + let invariants = invariants(domain.asserts.iter()); + + if !invariants.is_empty() { + println!("/// {invariants}"); + } + println!( + "type {} = {};\n", + domain.name.to_upper_camel_case(), + amqp_type_to_rust_type(&domain.kind), + ); + } + + Ok(()) +} + +fn class_defs(amqp: &Amqp) -> Result<()> { for class in &amqp.classes { let enum_name = class.name.to_upper_camel_case(); - println!("///////// ---- Class {enum_name}"); - println!("enum {enum_name} {{"); + println!("/// Index {}, handler = {}", class.index, class.handler); + println!("pub enum {enum_name} {{"); for method in &class.methods { let method_name = method.name.to_upper_camel_case(); + println!(" /// Index {}", method.index); print!(" {method_name}"); if method.fields.len() > 0 { println!(" {{"); for field in &method.fields { - let field_name = field.name.to_snake_case(); - println!(" {field_name}: (),"); + let field_name = snake_case(&field.name); + let (field_type, field_docs) = resolve_type( + amqp, + &field.domain.as_ref().or(field.kind.as_ref()).unwrap(), + field.asserts.as_ref(), + )?; + if !field_docs.is_empty() { + println!(" /// {field_docs}"); + } + println!(" {field_name}: {field_type},"); } - println!(" }}"); + println!(" }},"); } else { println!(","); } @@ -96,3 +136,75 @@ fn codegen(amqp: &Amqp) -> Result<()> { Ok(()) } + +fn amqp_type_to_rust_type<'a>(amqp_type: &str) -> &'static str { + match amqp_type { + "octet" => "u8", + "short" => "u16", + "long" => "u32", + "longlong" => "u64", + "bit" => "u8", + "shortstr" | "longstr" => "String", + "timestamp" => "u64", + "table" => "HashMap)>", + _ => unreachable!("invalid type {}", amqp_type), + } +} + +/// returns (type name, invariant docs) +fn resolve_type(amqp: &Amqp, domain: &str, asserts: &[Assert]) -> Result<(String, String)> { + let kind = amqp + .domains + .iter() + .find(|d| &d.name == domain) + .context("domain not found")?; + + let is_nonnull = is_nonnull(asserts.iter().chain(kind.asserts.iter())); + + let additional_docs = invariants(asserts.iter()); + + let type_name = domain.to_upper_camel_case(); + + Ok(( + if is_nonnull { + type_name + } else { + format!("Option<{type_name}>") + }, + additional_docs, + )) +} + +fn is_nonnull<'a>(mut asserts: impl Iterator) -> bool { + asserts.find(|assert| assert.check == "notnull").is_some() +} + +fn snake_case(ident: &str) -> String { + use heck::ToSnakeCase; + + if ident == "type" { + "r#type".to_string() + } else { + ident.to_snake_case() + } +} + +fn invariants<'a>(asserts: impl Iterator) -> String { + asserts + .filter_map(|assert| match &*assert.check { + "notnull" => None, + "length" => Some(format!( + "must be shorter than {}", + assert.value.as_ref().unwrap() + )), + "regexp" => Some(format!("must match `{}`", assert.value.as_ref().unwrap())), + "le" => Some(format!( + "must be less than the {} field of the method {}", + assert.method.as_ref().unwrap(), + assert.field.as_ref().unwrap() + )), + _ => unimplemented!(), + }) + .collect::>() + .join(", ") +} diff --git a/amqp_transport/src/classes/mod.rs b/amqp_transport/src/classes/mod.rs index 6108355..7127c85 100644 --- a/amqp_transport/src/classes/mod.rs +++ b/amqp_transport/src/classes/mod.rs @@ -1,7 +1,345 @@ -mod connection; +use std::collections::HashMap; -use crate::classes::connection::Connection; +type ClassId = u16; -pub enum Class { - Connection(Connection), +type ConsumerTag = String; + +type DeliveryTag = u64; + +/// must be shorter than 127, must match `^[a-zA-Z0-9-_.:]*$` +type ExchangeName = String; + +type MethodId = u16; + +type NoAck = u8; + +type NoLocal = u8; + +type NoWait = u8; + +/// must be shorter than 127 +type Path = String; + +type PeerProperties = HashMap)>; + +/// must be shorter than 127, must match `^[a-zA-Z0-9-_.:]*$` +type QueueName = String; + +type Redelivered = u8; + +type MessageCount = u32; + +type ReplyCode = u16; + +type ReplyText = String; + +type Bit = u8; + +type Octet = u8; + +type Short = u16; + +type Long = u32; + +type Longlong = u64; + +type Shortstr = String; + +type Longstr = String; + +type Timestamp = u64; + +type Table = HashMap)>; + +/// Index 10, handler = connection +pub enum Connection { + /// Index 10 + Start { + version_major: Option, + version_minor: Option, + server_properties: Option, + mechanisms: Longstr, + locales: Longstr, + }, + /// Index 11 + StartOk { + client_properties: Option, + mechanism: Shortstr, + response: Longstr, + locale: Shortstr, + }, + /// Index 20 + Secure { + challenge: Option, + }, + /// Index 21 + SecureOk { + response: Longstr, + }, + /// Index 30 + Tune { + channel_max: Option, + frame_max: Option, + heartbeat: Option, + }, + /// Index 31 + TuneOk { + /// must be less than the tune field of the method channel-max + channel_max: Short, + frame_max: Option, + heartbeat: Option, + }, + /// Index 40 + Open { + virtual_host: Path, + reserved_1: Option, + reserved_2: Option, + }, + /// Index 41 + OpenOk { + reserved_1: Option, + }, + /// Index 50 + Close { + reply_code: ReplyCode, + reply_text: ReplyText, + class_id: Option, + method_id: Option, + }, + /// Index 51 + CloseOk, + /// Index 60 + Blocked { + reason: Option, + }, + /// Index 61 + Unblocked, +} +/// Index 20, handler = channel +pub enum Channel { + /// Index 10 + Open { + reserved_1: Option, + }, + /// Index 11 + OpenOk { + reserved_1: Option, + }, + /// Index 20 + Flow { + active: Option, + }, + /// Index 21 + FlowOk { + active: Option, + }, + /// Index 40 + Close { + reply_code: ReplyCode, + reply_text: ReplyText, + class_id: Option, + method_id: Option, + }, + /// Index 41 + CloseOk, +} +/// Index 40, handler = channel +pub enum Exchange { + /// Index 10 + Declare { + reserved_1: Option, + exchange: ExchangeName, + r#type: Option, + passive: Option, + durable: Option, + reserved_2: Option, + reserved_3: Option, + no_wait: Option, + arguments: Option, + }, + /// Index 11 + DeclareOk, + /// Index 20 + Delete { + reserved_1: Option, + exchange: ExchangeName, + if_unused: Option, + no_wait: Option, + }, + /// Index 21 + DeleteOk, +} +/// Index 50, handler = channel +pub enum Queue { + /// Index 10 + Declare { + reserved_1: Option, + queue: Option, + passive: Option, + durable: Option, + exclusive: Option, + auto_delete: Option, + no_wait: Option, + arguments: Option
, + }, + /// Index 11 + DeclareOk { + queue: QueueName, + message_count: Option, + consumer_count: Option, + }, + /// Index 20 + Bind { + reserved_1: Option, + queue: Option, + exchange: Option, + routing_key: Option, + no_wait: Option, + arguments: Option
, + }, + /// Index 21 + BindOk, + /// Index 50 + Unbind { + reserved_1: Option, + queue: Option, + exchange: Option, + routing_key: Option, + arguments: Option
, + }, + /// Index 51 + UnbindOk, + /// Index 30 + Purge { + reserved_1: Option, + queue: Option, + no_wait: Option, + }, + /// Index 31 + PurgeOk { + message_count: Option, + }, + /// Index 40 + Delete { + reserved_1: Option, + queue: Option, + if_unused: Option, + if_empty: Option, + no_wait: Option, + }, + /// Index 41 + DeleteOk { + message_count: Option, + }, +} +/// Index 60, handler = channel +pub enum Basic { + /// Index 10 + Qos { + prefetch_size: Option, + prefetch_count: Option, + global: Option, + }, + /// Index 11 + QosOk, + /// Index 20 + Consume { + reserved_1: Option, + queue: Option, + consumer_tag: Option, + no_local: Option, + no_ack: Option, + exclusive: Option, + no_wait: Option, + arguments: Option
, + }, + /// Index 21 + ConsumeOk { + consumer_tag: Option, + }, + /// Index 30 + Cancel { + consumer_tag: Option, + no_wait: Option, + }, + /// Index 31 + CancelOk { + consumer_tag: Option, + }, + /// Index 40 + Publish { + reserved_1: Option, + exchange: Option, + routing_key: Option, + mandatory: Option, + immediate: Option, + }, + /// Index 50 + Return { + reply_code: ReplyCode, + reply_text: ReplyText, + exchange: Option, + routing_key: Option, + }, + /// Index 60 + Deliver { + consumer_tag: Option, + delivery_tag: Option, + redelivered: Option, + exchange: Option, + routing_key: Option, + }, + /// Index 70 + Get { + reserved_1: Option, + queue: Option, + no_ack: Option, + }, + /// Index 71 + GetOk { + delivery_tag: Option, + redelivered: Option, + exchange: Option, + routing_key: Option, + message_count: Option, + }, + /// Index 72 + GetEmpty { + reserved_1: Option, + }, + /// Index 80 + Ack { + delivery_tag: Option, + multiple: Option, + }, + /// Index 90 + Reject { + delivery_tag: Option, + requeue: Option, + }, + /// Index 100 + RecoverAsync { + requeue: Option, + }, + /// Index 110 + Recover { + requeue: Option, + }, + /// Index 111 + RecoverOk, +} +/// Index 90, handler = channel +pub enum Tx { + /// Index 10 + Select, + /// Index 11 + SelectOk, + /// Index 20 + Commit, + /// Index 21 + CommitOk, + /// Index 30 + Rollback, + /// Index 31 + RollbackOk, } diff --git a/amqp_transport/src/frame.rs b/amqp_transport/src/frame.rs index e35188e..6ae2042 100644 --- a/amqp_transport/src/frame.rs +++ b/amqp_transport/src/frame.rs @@ -8,7 +8,7 @@ mod frame_type { pub const METHOD: u8 = 1; pub const HEADER: u8 = 2; pub const BODY: u8 = 3; - pub const HEARTBEAT: u8 = 4; + pub const HEARTBEAT: u8 = 8; } #[derive(Debug, Clone, PartialEq, Eq)]