1use proc_macro2::{Literal, TokenStream};
16use quote::{format_ident, quote};
17
18use crate::{
19 emit::{EmitError, common::validate, tagged_fixture},
20 ir::{MessageSpec, MessageType},
21 name_conv,
22};
23
24const MAX_VERSION_CAP: i16 = 127;
29
30pub fn emit(specs: &[MessageSpec], schemas_version: &str) -> Result<String, EmitError> {
34 let banner = format!(
35 "// AUTO-GENERATED by krabka-protocol-codegen against {schemas_version}. Do not edit.\n\n"
36 );
37
38 let tokens = build_body(specs);
39
40 validate::<syn::File>(&tokens, "differential table")?;
43
44 Ok(format!("{banner}{tokens}"))
45}
46
47fn build_body(specs: &[MessageSpec]) -> TokenStream {
48 let mut ts = TokenStream::new();
49 ts.extend(build_preamble());
50 ts.extend(build_cases_table(specs));
51 ts.extend(build_encode_default(specs));
52 ts.extend(build_default_json_for(specs));
53 ts.extend(build_tagged_fixture(specs));
54 ts.extend(build_roundtrip(specs));
55 ts.extend(build_header_versions(specs));
56 ts.extend(build_strip_frame_header());
57 ts
58}
59
60fn build_preamble() -> TokenStream {
61 quote! {
62 use std::{error::Error, fmt};
63
64 use bytes::BytesMut;
65 use krabka_protocol::{Encode, ProtocolError};
66
67 #[derive(Debug, Clone, Copy)]
68 pub struct Case {
69 pub name: &'static str,
70 pub api_key: i16,
71 pub version: i16,
72 pub kind: Kind,
73 }
74
75 #[derive(Debug, Clone, Copy)]
76 pub enum Kind { Request, Response, RequestHeader, ResponseHeader }
77
78 #[doc = " The owned and the borrowed encoding of one message."]
79 pub type EncodedPair = (Vec<u8>, Vec<u8>);
80
81 #[doc = " Why a table lookup, encode or round trip failed."]
82 #[derive(Debug)]
83 pub enum TableError {
84 #[doc = " `name` identifies no message the table covers."]
85 UnknownMessage(String),
86 #[doc = " The codec rejected the message."]
87 Protocol(ProtocolError),
88 #[doc = " A decode left bytes behind."]
89 TrailingBytes { name: String, remaining: usize },
90 }
91
92 impl fmt::Display for TableError {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 Self::UnknownMessage(name) => write!(f, "unknown message {name}"),
96 Self::Protocol(e) => write!(f, "{e}"),
97 Self::TrailingBytes { name, remaining } => {
98 write!(f, "{name}: {remaining} bytes left after decode")
99 }
100 }
101 }
102 }
103
104 impl Error for TableError {
105 fn source(&self) -> Option<&(dyn Error + 'static)> {
106 match self {
107 Self::Protocol(e) => Some(e),
108 Self::UnknownMessage(_) | Self::TrailingBytes { .. } => None,
109 }
110 }
111 }
112
113 impl From<ProtocolError> for TableError {
114 fn from(e: ProtocolError) -> Self {
115 Self::Protocol(e)
116 }
117 }
118
119 fn encode_with<T: Encode>(message: &T, version: i16) -> Result<Vec<u8>, TableError> {
120 let mut buf = BytesMut::new();
121 message.encode(&mut buf, version)?;
122 Ok(buf.to_vec())
123 }
124
125 #[doc = " Fails when a decode of `name` left bytes in `rest`."]
126 fn expect_consumed(name: &str, rest: &[u8]) -> Result<(), TableError> {
127 if rest.is_empty() {
128 Ok(())
129 } else {
130 Err(TableError::TrailingBytes { name: name.to_owned(), remaining: rest.len() })
131 }
132 }
133 }
134}
135
136fn build_cases_table(specs: &[MessageSpec]) -> TokenStream {
137 let rows: Vec<TokenStream> = specs
138 .iter()
139 .flat_map(|s| {
140 if s.valid_versions.is_empty() || s.internal {
141 return Vec::new();
142 }
143 let kind_tokens: TokenStream = match s.message_type {
144 MessageType::Request => quote!(Kind::Request),
145 MessageType::Response => quote!(Kind::Response),
146 MessageType::Header => match s.name.as_str() {
147 "RequestHeader" => quote!(Kind::RequestHeader),
148 "ResponseHeader" => quote!(Kind::ResponseHeader),
149 _ => return Vec::new(), },
151 MessageType::Data => return Vec::new(),
152 };
153 let api_key = Literal::i16_unsuffixed(s.api_key.unwrap_or(0));
154 let name_lit = Literal::string(&s.name);
155 let max_v = s.valid_versions.max.min(MAX_VERSION_CAP);
156 (s.valid_versions.min..=max_v)
157 .map(|v| {
158 let v_lit = Literal::i16_unsuffixed(v);
159 let kind = kind_tokens.clone();
160 quote! {
161 Case { name: #name_lit, api_key: #api_key, version: #v_lit, kind: #kind },
162 }
163 })
164 .collect()
165 })
166 .collect();
167
168 quote! {
169 pub const CASES: &[Case] = &[
170 #(#rows)*
171 ];
172 }
173}
174
175fn table_specs(specs: &[MessageSpec]) -> impl Iterator<Item = &MessageSpec> {
177 specs.iter().filter(|s| {
178 !s.valid_versions.is_empty()
179 && !s.internal
180 && matches!(
181 s.message_type,
182 MessageType::Request | MessageType::Response | MessageType::Header
183 )
184 })
185}
186
187fn dispatch(
195 arms: &[TokenStream],
196 chunk: usize,
197 prefix: &str,
198 signature: (&TokenStream, &TokenStream, &TokenStream),
199 public: &TokenStream,
200) -> TokenStream {
201 let (params, args, ty) = signature;
202 let helpers: Vec<_> = arms
203 .chunks(chunk)
204 .enumerate()
205 .map(|(index, chunk)| {
206 let helper = format_ident!("{prefix}_{index}");
207 quote! {
208 fn #helper(name: &str, #params) -> Result<Option<#ty>, TableError> {
209 Ok(Some(match name {
210 #(#chunk)*
211 _ => return Ok(None),
212 }))
213 }
214 }
215 })
216 .collect();
217 let tries = (0..helpers.len()).map(|index| {
218 let helper = format_ident!("{prefix}_{index}");
219 quote! {
220 if let Some(value) = #helper(name, #args)? {
221 return Ok(value);
222 }
223 }
224 });
225 quote! {
226 #(#helpers)*
227 #public {
228 #(#tries)*
229 Err(TableError::UnknownMessage(name.to_owned()))
230 }
231 }
232}
233
234fn build_encode_default(specs: &[MessageSpec]) -> TokenStream {
235 let arms: Vec<TokenStream> = table_specs(specs)
236 .map(|s| {
237 let snake_ident = format_ident!("{}", name_conv::module_name(&s.name));
238 let type_ident = format_ident!("{}", name_conv::type_name(&s.name));
239 let name_lit = Literal::string(&s.name);
240 quote! {
241 #name_lit => encode_with(
242 &krabka_protocol::owned::#snake_ident::#type_ident::default(),
243 version,
244 )?,
245 }
246 })
247 .collect();
248 dispatch(
249 &arms,
250 8,
251 "encode_default",
252 ("e!(version: i16), "e!(version), "e!(Vec<u8>)),
253 "e! {
254 #[doc = " The default value of message `name`, encoded at `version`."]
255 #[doc = ""]
256 #[doc = " # Errors"]
257 #[doc = ""]
258 #[doc = " Fails when `name` is unknown or the encode fails."]
259 pub fn encode_default(name: &str, version: i16) -> Result<Vec<u8>, TableError>
260 },
261 )
262}
263
264fn build_default_json_for(specs: &[MessageSpec]) -> TokenStream {
265 let arms: Vec<TokenStream> = table_specs(specs)
266 .map(|s| {
267 let snake_ident = format_ident!("{}", name_conv::module_name(&s.name));
268 let name_lit = Literal::string(&s.name);
269 quote! {
270 #name_lit => krabka_protocol::owned::#snake_ident::default_json(version),
271 }
272 })
273 .collect();
274 dispatch(
275 &arms,
276 60,
277 "default_json_for",
278 (
279 "e!(version: i16),
280 "e!(version),
281 "e!(::serde_json::Value),
282 ),
283 "e! {
284 #[doc = " The JSON the oracle reads as the default value of message `name`."]
285 #[doc = ""]
286 #[doc = " # Errors"]
287 #[doc = ""]
288 #[doc = " Fails when `name` is unknown."]
289 pub fn default_json_for(name: &str, version: i16) -> Result<::serde_json::Value, TableError>
290 },
291 )
292}
293
294fn build_tagged_fixture(specs: &[MessageSpec]) -> TokenStream {
297 let tagged: Vec<&MessageSpec> = specs
298 .iter()
299 .filter(|s| {
300 !s.valid_versions.is_empty() && !s.internal && tagged_fixture::has_tagged_field(s)
301 })
302 .collect();
303
304 let rows = tagged.iter().flat_map(|s| {
305 let name_lit = Literal::string(&s.name);
306 let max_v = s.valid_versions.max.min(MAX_VERSION_CAP);
307 (s.valid_versions.min..=max_v).map(move |v| {
308 let v_lit = Literal::i16_unsuffixed(v);
309 quote! { TaggedCase { name: #name_lit, version: #v_lit }, }
310 })
311 });
312
313 let encode_arms: Vec<TokenStream> = tagged
314 .iter()
315 .map(|s| {
316 let snake_ident = format_ident!("{}", name_conv::module_name(&s.name));
317 let type_ident = format_ident!("{}", name_conv::type_name(&s.name));
318 let name_lit = Literal::string(&s.name);
319 quote! {
320 #name_lit => {
321 let owned = krabka_protocol::owned::#snake_ident::tagged_fixture();
322 let max = krabka_protocol::owned::#snake_ident::MAX_VERSION;
323 let full = encode_with(&owned, max)?;
324 let mut cur = full.as_slice();
325 let borrowed =
326 <krabka_protocol::borrowed::#snake_ident::#type_ident as krabka_protocol::DecodeBorrow>::decode_borrow(&mut cur, max)?;
327 expect_consumed(name, cur)?;
328 (encode_with(&owned, version)?, encode_with(&borrowed, version)?)
329 }
330 }
331 })
332 .collect();
333 let encode = dispatch(
334 &encode_arms,
335 6,
336 "encode_tagged_fixture",
337 (
338 "e!(version: i16),
339 "e!(version),
340 "e!(EncodedPair),
341 ),
342 "e! {
343 #[doc = " The owned `tagged_fixture()` encoded at `version`, and the borrowed flavor of"]
344 #[doc = " the same message encoded at `version`."]
345 #[doc = ""]
346 #[doc = " The borrowed message is decoded from the owned bytes at the highest version,"]
347 #[doc = " where every tag is in range."]
348 #[doc = ""]
349 #[doc = " # Errors"]
350 #[doc = ""]
351 #[doc = " Fails when `name` has no tagged field, or an encode or decode fails."]
352 pub fn encode_tagged_fixture(name: &str, version: i16) -> Result<EncodedPair, TableError>
353 },
354 );
355
356 let json_arms = tagged.iter().map(|s| {
357 let snake_ident = format_ident!("{}", name_conv::module_name(&s.name));
358 let name_lit = Literal::string(&s.name);
359 quote! {
360 #name_lit => Ok(krabka_protocol::owned::#snake_ident::tagged_fixture_json(version)),
361 }
362 });
363
364 quote! {
365 #[derive(Debug, Clone, Copy)]
366 pub struct TaggedCase {
367 pub name: &'static str,
368 pub version: i16,
369 }
370
371 pub const TAGGED_CASES: &[TaggedCase] = &[
372 #(#rows)*
373 ];
374
375 #encode
376
377 #[doc = " The JSON form of the tagged fixture of message `name` at `version`."]
378 #[doc = ""]
379 #[doc = " # Errors"]
380 #[doc = ""]
381 #[doc = " Fails when `name` has no tagged field."]
382 pub fn tagged_fixture_json_for(name: &str, version: i16) -> Result<::serde_json::Value, TableError> {
383 match name {
384 #(#json_arms)*
385 _ => Err(TableError::UnknownMessage(name.to_owned())),
386 }
387 }
388 }
389}
390
391fn build_roundtrip(specs: &[MessageSpec]) -> TokenStream {
392 let arms: Vec<TokenStream> = table_specs(specs)
393 .map(|s| {
394 let snake_ident = format_ident!("{}", name_conv::module_name(&s.name));
395 let type_ident = format_ident!("{}", name_conv::type_name(&s.name));
396 let name_lit = Literal::string(&s.name);
397 quote! {
398 #name_lit => {
399 let mut cur = bytes;
400 let msg = <krabka_protocol::owned::#snake_ident::#type_ident as krabka_protocol::Decode>::decode(&mut cur, version)?;
401 expect_consumed(name, cur)?;
402 encode_with(&msg, version)?
403 }
404 }
405 })
406 .collect();
407 dispatch(
408 &arms,
409 6,
410 "roundtrip",
411 (
412 "e!(version: i16, bytes: &[u8]),
413 "e!(version, bytes),
414 "e!(Vec<u8>),
415 ),
416 "e! {
417 #[doc = " Decode `bytes` as message `name` at `version` and encode it again."]
418 #[doc = ""]
419 #[doc = " # Errors"]
420 #[doc = ""]
421 #[doc = " Fails when `name` is unknown, the decode or encode fails, or the decode"]
422 #[doc = " leaves bytes behind."]
423 pub fn roundtrip(name: &str, version: i16, bytes: &[u8]) -> Result<Vec<u8>, TableError>
424 },
425 )
426}
427
428fn build_header_versions(specs: &[MessageSpec]) -> TokenStream {
429 let flex_entries = |message_type: MessageType| -> Vec<TokenStream> {
430 specs
431 .iter()
432 .filter(|s| {
433 !s.valid_versions.is_empty()
434 && !s.internal
435 && s.message_type == message_type
436 && s.name != "ApiVersionsResponse"
437 })
438 .map(|s| {
439 let fm_lit = Literal::i16_unsuffixed(super::owned::flex_min(s));
440 let name_lit = Literal::string(&s.name);
441 quote! { (#name_lit, #fm_lit), }
442 })
443 .collect()
444 };
445 let req_entries = flex_entries(MessageType::Request);
446 let resp_entries = flex_entries(MessageType::Response);
447
448 quote! {
449 const REQUEST_FLEX_MIN: &[(&str, i16)] = &[#(#req_entries)*];
450 const RESPONSE_FLEX_MIN: &[(&str, i16)] = &[#(#resp_entries)*];
451
452 fn header_flex_min(table: &[(&str, i16)], name: &str) -> Result<i16, TableError> {
453 table
454 .iter()
455 .find_map(|(candidate, minimum)| (*candidate == name).then_some(*minimum))
456 .ok_or_else(|| TableError::UnknownMessage(name.to_owned()))
457 }
458
459 #[doc = " The request header version that request `name` uses at `version`."]
460 #[doc = ""]
461 #[doc = " # Errors"]
462 #[doc = ""]
463 #[doc = " Fails when `name` is not a known request."]
464 pub fn request_header_version(name: &str, version: i16) -> Result<i16, TableError> {
465 if name == "ControlledShutdownRequest" && version == 0 {
466 return Ok(0);
467 }
468 let minimum = header_flex_min(REQUEST_FLEX_MIN, name)?;
469 Ok(if minimum == i16::MIN || version >= minimum { 2 } else { 1 })
470 }
471
472 #[doc = " The response header version that response `name` uses at `version`."]
473 #[doc = ""]
474 #[doc = " # Errors"]
475 #[doc = ""]
476 #[doc = " Fails when `name` is not a known response."]
477 pub fn response_header_version(name: &str, version: i16) -> Result<i16, TableError> {
478 if name == "ApiVersionsResponse" {
479 return Ok(0);
480 }
481 let minimum = header_flex_min(RESPONSE_FLEX_MIN, name)?;
482 Ok(i16::from(minimum != i16::MIN && version >= minimum))
483 }
484 }
485}
486
487fn build_strip_frame_header() -> TokenStream {
488 quote! {
489 #[doc = " Decode and discard the request/response header from a full frame body,"]
490 #[doc = " returning the remaining message bytes."]
491 #[doc = ""]
492 #[doc = " # Errors"]
493 #[doc = ""]
494 #[doc = " Fails when the frame header is malformed or `name` is unknown."]
495 pub fn strip_frame_header(
496 name: &str,
497 version: i16,
498 is_request: bool,
499 frame: &[u8],
500 ) -> Result<Vec<u8>, TableError> {
501 let mut cur = frame;
502 if is_request {
503 let hv = request_header_version(name, version)?;
504 <krabka_protocol::owned::request_header::RequestHeader as krabka_protocol::Decode>::decode(&mut cur, hv)?;
505 } else {
506 let hv = response_header_version(name, version)?;
507 <krabka_protocol::owned::response_header::ResponseHeader as krabka_protocol::Decode>::decode(&mut cur, hv)?;
508 }
509 Ok(cur.to_vec())
510 }
511 }
512}