summaryrefslogtreecommitdiff
path: root/crates/tor-rpcbase/src/dispatch/description.rs
blob: 67974698b67e77a4bc66f86e780e037169cd2a2c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Types and code for describing our type-based dispatch system.

use std::collections::{BTreeMap, BTreeSet};

use serde::Serialize;

use crate::{MethodInfo_, NoUpdates, method::method_info_by_typeid};

/// A table describing, for a single RPC method,
/// which types it expects and returns, and which objects it applies to.
///
/// This is, for now, a serialize-only type; it does nothing else.
///
/// See [`RpcDispatchInformation`] for caveats about type names.
#[derive(Serialize, Debug, Clone)]
struct MethodDescription {
    /// The type representing an invocation of this method.
    ///
    /// Its fields are the method's parameters.
    method_type: String,
    /// The type that this method returns on successful completion.
    output_type: String,
    /// The type (if any) that this method delivers as an incremental status update.
    update_type: Option<String>,
    /// A list of the types of Objects that this method can be applied to.
    applies_to_object_types: BTreeSet<String>,
}

/// A table describing the the set of RPC methods available,
/// which types they expect and return, and which objects they apply to.
///
/// This is, for now, a serialize-only type; it does nothing else.
///
/// All "type names" in this object refer to a Rust type in Arti.
/// These Rust types are mainly useful for finding the relevant types
/// in the generated Arti documentation.
/// They are not guaranteed to be stable across Arti versions:
/// for example, we might rename a type, or put it in a different module or crate.
/// They are not guaranteed to be stable across Rust versions:
/// see the caveats in [`std::any::type_name`].
#[derive(Serialize, Debug, Clone)]
pub struct RpcDispatchInformation {
    /// A map from RPC method name (such as "arti:foo") to a description of that method.
    methods: BTreeMap<String, MethodDescription>,

    /// A map from an object type to a list of the types that object can delegate to.
    delegations: BTreeMap<String, BTreeSet<String>>,
}

impl super::DispatchTable {
    /// Return a description for all of the RPC methods available,
    /// which types they expect and return, and which objects they apply to.
    ///
    /// Currently, the resulting object is good for nothing but serialization.
    pub fn dispatch_information(&self) -> RpcDispatchInformation {
        let mut methods = BTreeMap::new();
        for invoker_ent in self.map.values() {
            let Some(method_info) = method_info_by_typeid(invoker_ent.invoker.method_type()) else {
                continue; // This isn't an RpcMethod.
            };

            let rpc_method_name = method_info.method_name.to_owned();
            let (object_type_name, method_type_name) =
                invoker_ent.invoker.object_and_method_type_names();
            let description = methods
                .entry(rpc_method_name)
                .or_insert_with(|| MethodDescription::new(method_type_name, method_info));
            description.push_object_type(object_type_name);
        }

        let mut delegations = BTreeMap::new();
        for note in inventory::iter::<DelegationNote>() {
            let set = delegations
                .entry((note.from_type_name)().to_string())
                .or_insert_with(BTreeSet::new);
            set.insert((note.to_type_name)().to_string());
        }

        RpcDispatchInformation {
            methods,
            delegations,
        }
    }
}

impl MethodDescription {
    /// Construct a new `MethodDescription`.
    fn new(method_type_name: &str, info: &MethodInfo_) -> Self {
        let method_type_name = method_type_name.to_owned();
        let output_type_name = (info.output_name)().to_owned();
        let update_type_name = {
            let name = (info.update_name)();
            if name == std::any::type_name::<NoUpdates>() {
                None
            } else {
                Some(name.to_owned())
            }
        };

        MethodDescription {
            method_type: method_type_name,
            output_type: output_type_name,
            update_type: update_type_name,
            applies_to_object_types: Default::default(),
        }
    }

    /// Add `object_type_name` to the list of object types this method applies to.
    fn push_object_type(&mut self, object_type_name: &str) {
        self.applies_to_object_types
            .insert(object_type_name.to_owned());
    }
}

/// Helper to implement RpcDispatchInformation.
#[derive(Debug)]
#[doc(hidden)]
#[allow(clippy::exhaustive_structs)]
pub struct DelegationNote {
    /// Name of the type we're delegating from.
    ///
    /// (We use a fn() here since std::any::type_name isn't yet const.)
    pub from_type_name: fn() -> &'static str,
    /// Name of the type we're delegating to.
    pub to_type_name: fn() -> &'static str,
}
inventory::collect!(DelegationNote);

/// Helper: Declare that `$from_type` (which must implement `Object`)
/// can delegate to `$to_type` (which also must implement `Object`).
///
/// This declaration is used only to help implement `RpcDispatchInformation`.
#[macro_export]
#[doc(hidden)]
macro_rules! register_delegation_note {
    { $from_type:ty, $to_type:ty } => {
        $crate::inventory::submit!{$crate::DelegationNote {
            from_type_name: std::any::type_name::<$from_type>,
            to_type_name: std::any::type_name::<$to_type>,
        }}
    }
}