Skip to main content

netget/server/thrift/
idl.rs

1//! A Thrift IDL subset, parsed at startup: namespaces (ignored), typedefs, consts (skipped),
2//! enums, structs, unions, exceptions and services (with `extends`, `oneway`, `throws`).
3//! `include` is refused: the IDL must be self-contained.
4use anyhow::{bail, ensure, Context, Result};
5use std::collections::HashMap;
6
7pub const MAX_IDL: usize = 256 * 1024;
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum Type {
11    Bool,
12    Byte,
13    I16,
14    I32,
15    I64,
16    Double,
17    String,
18    Binary,
19    Uuid,
20    List(Box<Type>),
21    Set(Box<Type>),
22    Map(Box<Type>, Box<Type>),
23    /// A struct, union or exception.
24    Struct(String),
25    Enum(String),
26}
27
28#[derive(Debug, Clone)]
29pub struct Field {
30    pub id: i16,
31    pub name: String,
32    pub ty: Type,
33    pub required: bool,
34}
35
36#[derive(Debug, Clone)]
37pub struct Function {
38    pub name: String,
39    pub oneway: bool,
40    /// None for void.
41    pub returns: Option<Type>,
42    pub args: Vec<Field>,
43    pub throws: Vec<Field>,
44}
45
46#[derive(Debug, Clone, Default)]
47pub struct Service {
48    pub name: String,
49    pub extends: Option<String>,
50    pub functions: Vec<Function>,
51}
52
53#[derive(Debug, Default)]
54pub struct Idl {
55    pub structs: HashMap<String, Vec<Field>>,
56    pub unions: Vec<String>,
57    pub exceptions: Vec<String>,
58    pub enums: HashMap<String, Vec<(String, i32)>>,
59    pub services: Vec<Service>,
60    typedefs: HashMap<String, Type>,
61}
62
63impl Idl {
64    pub fn service(&self, name: Option<&str>) -> Result<&Service> {
65        match name {
66            Some(n) => self
67                .services
68                .iter()
69                .find(|s| s.name == n)
70                .with_context(|| format!("the IDL has no service {n}")),
71            None => self.services.last().context("the IDL defines no service"),
72        }
73    }
74    /// A function of `service` or of a service it extends.
75    pub fn function<'a>(&'a self, service: &'a Service, name: &str) -> Option<&'a Function> {
76        let mut s = Some(service);
77        let mut hops = 0;
78        while let (Some(svc), true) = (s, hops < 16) {
79            if let Some(f) = svc.functions.iter().find(|f| f.name == name) {
80                return Some(f);
81            }
82            s = svc.extends.as_deref().and_then(|e| {
83                self.services
84                    .iter()
85                    .find(|x| x.name == e.rsplit('.').next().unwrap_or(e))
86            });
87            hops += 1;
88        }
89        None
90    }
91    pub fn enum_name(&self, name: &str, value: i32) -> Option<&str> {
92        self.enums
93            .get(name)?
94            .iter()
95            .find(|(_, v)| *v == value)
96            .map(|(n, _)| n.as_str())
97    }
98    pub fn enum_value(&self, name: &str, label: &str) -> Option<i32> {
99        self.enums
100            .get(name)?
101            .iter()
102            .find(|(n, _)| n == label)
103            .map(|(_, v)| *v)
104    }
105}
106
107#[derive(Debug, Clone, PartialEq)]
108enum Tok {
109    Ident(String),
110    Int(i64),
111    Str(String),
112    Sym(char),
113}
114
115fn lex(src: &str) -> Result<Vec<Tok>> {
116    let b: Vec<char> = src.chars().collect();
117    let mut out = Vec::new();
118    let mut i = 0;
119    while i < b.len() {
120        let c = b[i];
121        if c.is_whitespace() {
122            i += 1;
123        } else if c == '#' || (c == '/' && b.get(i + 1) == Some(&'/')) {
124            while i < b.len() && b[i] != '\n' {
125                i += 1;
126            }
127        } else if c == '/' && b.get(i + 1) == Some(&'*') {
128            i += 2;
129            while i + 1 < b.len() && !(b[i] == '*' && b[i + 1] == '/') {
130                i += 1;
131            }
132            i += 2;
133        } else if c == '"' || c == '\'' {
134            let q = c;
135            let start = i + 1;
136            i += 1;
137            while i < b.len() && b[i] != q {
138                i += 1;
139            }
140            ensure!(i < b.len(), "unterminated string literal");
141            out.push(Tok::Str(b[start..i].iter().collect()));
142            i += 1;
143        } else if c.is_ascii_digit()
144            || (c == '-' && b.get(i + 1).is_some_and(|d| d.is_ascii_digit()))
145        {
146            let start = i;
147            i += 1;
148            while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == '.' || b[i] == 'x') {
149                i += 1;
150            }
151            let text: String = b[start..i].iter().collect();
152            let n = if let Some(hex) = text.strip_prefix("0x") {
153                i64::from_str_radix(hex, 16)
154            } else {
155                text.parse::<i64>()
156            };
157            // Float constants only appear as defaults, which are skipped.
158            out.push(Tok::Int(n.unwrap_or(0)));
159        } else if c.is_alphabetic() || c == '_' {
160            let start = i;
161            while i < b.len() && (b[i].is_alphanumeric() || b[i] == '_' || b[i] == '.') {
162                i += 1;
163            }
164            out.push(Tok::Ident(b[start..i].iter().collect()));
165        } else {
166            out.push(Tok::Sym(c));
167            i += 1;
168        }
169    }
170    Ok(out)
171}
172
173struct Parser {
174    toks: Vec<Tok>,
175    pos: usize,
176}
177
178impl Parser {
179    fn peek(&self) -> Option<&Tok> {
180        self.toks.get(self.pos)
181    }
182    fn next(&mut self) -> Result<Tok> {
183        let t = self
184            .toks
185            .get(self.pos)
186            .cloned()
187            .context("unexpected end of IDL")?;
188        self.pos += 1;
189        Ok(t)
190    }
191    fn ident(&mut self) -> Result<String> {
192        match self.next()? {
193            Tok::Ident(s) => Ok(s),
194            t => bail!("expected a name, found {t:?}"),
195        }
196    }
197    fn sym(&mut self, c: char) -> Result<()> {
198        match self.next()? {
199            Tok::Sym(s) if s == c => Ok(()),
200            t => bail!("expected '{c}', found {t:?}"),
201        }
202    }
203    fn eat(&mut self, c: char) -> bool {
204        if self.peek() == Some(&Tok::Sym(c)) {
205            self.pos += 1;
206            true
207        } else {
208            false
209        }
210    }
211    fn eat_word(&mut self, w: &str) -> bool {
212        if matches!(self.peek(), Some(Tok::Ident(s)) if s == w) {
213            self.pos += 1;
214            true
215        } else {
216            false
217        }
218    }
219    fn separator(&mut self) {
220        let _ = self.eat(',') || self.eat(';');
221    }
222    /// Skip a constant value (default values and const bodies), including lists and maps.
223    fn skip_value(&mut self) -> Result<()> {
224        match self.next()? {
225            Tok::Sym('[') | Tok::Sym('{') => {
226                let mut depth = 1;
227                while depth > 0 {
228                    match self.next()? {
229                        Tok::Sym('[') | Tok::Sym('{') => depth += 1,
230                        Tok::Sym(']') | Tok::Sym('}') => depth -= 1,
231                        _ => {}
232                    }
233                }
234                Ok(())
235            }
236            _ => Ok(()),
237        }
238    }
239    /// Skip a trailing annotation list `( key = "value", ... )`.
240    fn skip_annotations(&mut self) -> Result<()> {
241        if self.eat('(') {
242            while !self.eat(')') {
243                self.next()?;
244            }
245        }
246        Ok(())
247    }
248    fn ty(&mut self, depth: usize) -> Result<Type> {
249        ensure!(depth < 32, "types nest too deeply");
250        let name = self.ident()?;
251        let t = match name.as_str() {
252            "bool" => Type::Bool,
253            "byte" | "i8" => Type::Byte,
254            "i16" => Type::I16,
255            "i32" => Type::I32,
256            "i64" => Type::I64,
257            "double" => Type::Double,
258            "string" => Type::String,
259            "binary" => Type::Binary,
260            "uuid" => Type::Uuid,
261            "list" | "set" => {
262                self.sym('<')?;
263                let e = self.ty(depth + 1)?;
264                self.sym('>')?;
265                if name == "list" {
266                    Type::List(Box::new(e))
267                } else {
268                    Type::Set(Box::new(e))
269                }
270            }
271            "map" => {
272                self.sym('<')?;
273                let k = self.ty(depth + 1)?;
274                self.sym(',')?;
275                let v = self.ty(depth + 1)?;
276                self.sym('>')?;
277                Type::Map(Box::new(k), Box::new(v))
278            }
279            other => Type::Struct(other.rsplit('.').next().unwrap_or(other).to_owned()),
280        };
281        self.skip_annotations()?;
282        Ok(t)
283    }
284    fn fields(&mut self, close: char) -> Result<Vec<Field>> {
285        let mut out: Vec<Field> = Vec::new();
286        while !self.eat(close) {
287            let id = match self.next()? {
288                Tok::Int(n) => i16::try_from(n).context("field id out of range")?,
289                t => {
290                    bail!("expected a field id, found {t:?} (implicit field ids are not supported)")
291                }
292            };
293            self.sym(':')?;
294            let required = self.eat_word("required");
295            let _ = self.eat_word("optional");
296            let ty = self.ty(0)?;
297            let name = self.ident()?;
298            if self.eat('=') {
299                self.skip_value()?;
300            }
301            self.skip_annotations()?;
302            self.separator();
303            ensure!(
304                !out.iter().any(|f| f.id == id),
305                "field id {id} is used twice"
306            );
307            out.push(Field {
308                id,
309                name,
310                ty,
311                required,
312            });
313        }
314        Ok(out)
315    }
316}
317
318pub fn parse(src: &str) -> Result<Idl> {
319    ensure!(
320        src.len() <= MAX_IDL,
321        "the IDL is over {} KiB",
322        MAX_IDL / 1024
323    );
324    let mut p = Parser {
325        toks: lex(src)?,
326        pos: 0,
327    };
328    let mut idl = Idl::default();
329    while let Some(t) = p.peek().cloned() {
330        let Tok::Ident(word) = t else {
331            bail!("unexpected {t:?} at the top level")
332        };
333        p.pos += 1;
334        match word.as_str() {
335            "namespace" => {
336                p.next()?;
337                p.next()?;
338            }
339            "include" | "cpp_include" => {
340                bail!("include is not supported: give one self-contained IDL")
341            }
342            "typedef" => {
343                let t = p.ty(0)?;
344                let name = p.ident()?;
345                p.skip_annotations()?;
346                p.separator();
347                idl.typedefs.insert(name, t);
348            }
349            "const" => {
350                p.ty(0)?;
351                p.ident()?;
352                p.sym('=')?;
353                p.skip_value()?;
354                p.separator();
355            }
356            "enum" => {
357                let name = p.ident()?;
358                p.sym('{')?;
359                let mut values = Vec::new();
360                let mut next = 0i32;
361                while !p.eat('}') {
362                    let label = p.ident()?;
363                    if p.eat('=') {
364                        next = match p.next()? {
365                            Tok::Int(n) => i32::try_from(n).context("enum value out of range")?,
366                            t => bail!("expected an enum value, found {t:?}"),
367                        };
368                    }
369                    p.skip_annotations()?;
370                    values.push((label, next));
371                    next += 1;
372                    p.separator();
373                }
374                p.skip_annotations()?;
375                idl.enums.insert(name, values);
376            }
377            "struct" | "union" | "exception" => {
378                let name = p.ident()?;
379                p.sym('{')?;
380                let fields = p.fields('}')?;
381                p.skip_annotations()?;
382                if word == "union" {
383                    idl.unions.push(name.clone());
384                }
385                if word == "exception" {
386                    idl.exceptions.push(name.clone());
387                }
388                idl.structs.insert(name, fields);
389            }
390            "service" => {
391                let name = p.ident()?;
392                let extends = if p.eat_word("extends") {
393                    Some(p.ident()?)
394                } else {
395                    None
396                };
397                p.sym('{')?;
398                let mut functions = Vec::new();
399                while !p.eat('}') {
400                    let oneway = p.eat_word("oneway");
401                    let returns = if p.eat_word("void") {
402                        None
403                    } else {
404                        Some(p.ty(0)?)
405                    };
406                    let fname = p.ident()?;
407                    p.sym('(')?;
408                    let args = p.fields(')')?;
409                    let throws = if p.eat_word("throws") {
410                        p.sym('(')?;
411                        p.fields(')')?
412                    } else {
413                        vec![]
414                    };
415                    p.skip_annotations()?;
416                    p.separator();
417                    ensure!(
418                        !oneway || (returns.is_none() && throws.is_empty()),
419                        "oneway {fname} must be void without throws"
420                    );
421                    functions.push(Function {
422                        name: fname,
423                        oneway,
424                        returns,
425                        args,
426                        throws,
427                    });
428                }
429                p.skip_annotations()?;
430                idl.services.push(Service {
431                    name,
432                    extends,
433                    functions,
434                });
435            }
436            other => bail!("unsupported IDL construct {other:?}"),
437        }
438    }
439    idl.resolve()?;
440    Ok(idl)
441}
442
443impl Idl {
444    /// Replace typedef names and tell enums from structs, everywhere.
445    fn resolve(&mut self) -> Result<()> {
446        let typedefs = self.typedefs.clone();
447        let enums: Vec<String> = self.enums.keys().cloned().collect();
448        let structs: Vec<String> = self.structs.keys().cloned().collect();
449        let fix = |t: &mut Type| -> Result<()> { resolve_type(t, &typedefs, &enums, &structs, 0) };
450        for fields in self.structs.values_mut() {
451            for f in fields.iter_mut() {
452                fix(&mut f.ty)?;
453            }
454        }
455        for s in &mut self.services {
456            for f in &mut s.functions {
457                if let Some(r) = &mut f.returns {
458                    fix(r)?;
459                }
460                for a in f.args.iter_mut().chain(f.throws.iter_mut()) {
461                    fix(&mut a.ty)?;
462                }
463            }
464        }
465        Ok(())
466    }
467}
468
469fn resolve_type(
470    t: &mut Type,
471    typedefs: &HashMap<String, Type>,
472    enums: &[String],
473    structs: &[String],
474    depth: usize,
475) -> Result<()> {
476    ensure!(depth < 32, "typedefs nest too deeply");
477    match t {
478        Type::List(e) | Type::Set(e) => resolve_type(e, typedefs, enums, structs, depth + 1),
479        Type::Map(k, v) => {
480            resolve_type(k, typedefs, enums, structs, depth + 1)?;
481            resolve_type(v, typedefs, enums, structs, depth + 1)
482        }
483        Type::Struct(name) => {
484            if let Some(real) = typedefs.get(name) {
485                *t = real.clone();
486                resolve_type(t, typedefs, enums, structs, depth + 1)
487            } else if enums.contains(name) {
488                *t = Type::Enum(name.clone());
489                Ok(())
490            } else {
491                ensure!(structs.contains(name), "unknown type {name}");
492                Ok(())
493            }
494        }
495        _ => Ok(()),
496    }
497}