apollo_federation/connectors/json_selection/
methods.rs

1use serde_json_bytes::Value as JSON;
2use shape::Shape;
3
4use super::ApplyToError;
5use super::MethodArgs;
6use super::VarsWithPathsMap;
7use super::immutable::InputPath;
8use super::location::WithRange;
9use crate::connectors::json_selection::ShapeContext;
10use crate::connectors::spec::ConnectSpec;
11
12mod common;
13
14// Two kinds of methods: public ones and not-yet-public ones. The future ones
15// have proposed implementations and tests, and some are even used within the
16// tests of other methods, but are not yet exposed for use in connector schemas.
17// Graduating to public status requires updated documentation, careful review,
18// and team discussion to make sure the method is one we want to support
19// long-term. Once we have a better story for checking method type signatures
20// and versioning any behavioral changes, we should be able to expand/improve
21// the list of public::* methods more quickly/confidently.
22mod future;
23mod public;
24
25#[cfg(test)]
26mod tests;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub(super) enum ArrowMethod {
30    // Public methods:
31    As,
32    Echo,
33    Map,
34    Match,
35    First,
36    Last,
37    Slice,
38    Size,
39    Entries,
40    JsonStringify,
41    JoinNotNull,
42    Filter,
43    Find,
44    Gte,
45    Lte,
46    Eq,
47    Ne,
48    Or,
49    And,
50    Gt,
51    Lt,
52    Not,
53    In,
54    Contains,
55    Get,
56    ToString,
57    ParseInt,
58    Add,
59    Sub,
60    Mul,
61    Div,
62    Mod,
63
64    // Future methods:
65    TypeOf,
66    MatchIf,
67    Has,
68    Keys,
69    Values,
70}
71
72#[macro_export]
73macro_rules! impl_arrow_method {
74    ($struct_name:ident, $impl_fn_name:ident, $shape_fn_name:ident) => {
75        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
76        pub(crate) struct $struct_name;
77        impl $crate::connectors::json_selection::methods::ArrowMethodImpl for $struct_name {
78            fn apply(
79                &self,
80                method_name: &WithRange<String>,
81                method_args: Option<&MethodArgs>,
82                data: &JSON,
83                vars: &VarsWithPathsMap,
84                input_path: &InputPath<JSON>,
85                spec: $crate::connectors::spec::ConnectSpec,
86            ) -> (Option<JSON>, Vec<ApplyToError>) {
87                $impl_fn_name(method_name, method_args, data, vars, input_path, spec)
88            }
89
90            fn shape(
91                &self,
92                context: &$crate::connectors::json_selection::apply_to::ShapeContext,
93                method_name: &WithRange<String>,
94                method_args: Option<&MethodArgs>,
95                input_shape: Shape,
96                dollar_shape: Shape,
97            ) -> Shape {
98                $shape_fn_name(context, method_name, method_args, input_shape, dollar_shape)
99            }
100        }
101    };
102}
103
104#[allow(dead_code)] // method type-checking disabled until we add name resolution
105pub(super) trait ArrowMethodImpl {
106    fn apply(
107        &self,
108        method_name: &WithRange<String>,
109        method_args: Option<&MethodArgs>,
110        data: &JSON,
111        vars: &VarsWithPathsMap,
112        input_path: &InputPath<JSON>,
113        spec: ConnectSpec,
114    ) -> (Option<JSON>, Vec<ApplyToError>);
115
116    fn shape(
117        &self,
118        context: &ShapeContext,
119        // Shape processing errors for methods can benefit from knowing the name
120        // of the method and its source range. Note that ArrowMethodImpl::shape
121        // is invoked for every invocation of a method, with appropriately
122        // different source ranges.
123        method_name: &WithRange<String>,
124        // Most methods implementing ArrowMethodImpl::shape will need to know
125        // the shapes of their arguments, which can be computed from MethodArgs
126        // using the compute_output_shape method.
127        method_args: Option<&MethodArgs>,
128        // The input_shape is the shape of the @ variable, or the value from the
129        // left hand side of the -> token.
130        input_shape: Shape,
131        // The dollar_shape is the shape of the $ variable, or the input object
132        // associated with the closest enclosing subselection.
133        dollar_shape: Shape,
134    ) -> Shape;
135}
136
137// This Deref implementation allows us to call .apply(...) directly on the
138// ArrowMethod enum.
139impl std::ops::Deref for ArrowMethod {
140    type Target = dyn ArrowMethodImpl;
141
142    fn deref(&self) -> &Self::Target {
143        match self {
144            // Public methods:
145            Self::As => &public::AsMethod,
146            Self::Echo => &public::EchoMethod,
147            Self::Map => &public::MapMethod,
148            Self::Match => &public::MatchMethod,
149            Self::First => &public::FirstMethod,
150            Self::Last => &public::LastMethod,
151            Self::Slice => &public::SliceMethod,
152            Self::Size => &public::SizeMethod,
153            Self::Entries => &public::EntriesMethod,
154            Self::JsonStringify => &public::JsonStringifyMethod,
155            Self::JoinNotNull => &public::JoinNotNullMethod,
156            Self::Filter => &public::FilterMethod,
157            Self::Find => &public::FindMethod,
158            Self::Gte => &public::GteMethod,
159            Self::Lte => &public::LteMethod,
160            Self::Eq => &public::EqMethod,
161            Self::Ne => &public::NeMethod,
162            Self::Or => &public::OrMethod,
163            Self::And => &public::AndMethod,
164            Self::Gt => &public::GtMethod,
165            Self::Lt => &public::LtMethod,
166            Self::Not => &public::NotMethod,
167            Self::In => &public::InMethod,
168            Self::Contains => &public::ContainsMethod,
169            Self::Get => &public::GetMethod,
170            Self::ToString => &public::ToStringMethod,
171            Self::ParseInt => &public::ParseIntMethod,
172            Self::Add => &public::AddMethod,
173            Self::Sub => &public::SubMethod,
174            Self::Mul => &public::MulMethod,
175            Self::Div => &public::DivMethod,
176            Self::Mod => &public::ModMethod,
177
178            // Future methods:
179            Self::TypeOf => &future::TypeOfMethod,
180            Self::MatchIf => &future::MatchIfMethod,
181            Self::Has => &future::HasMethod,
182            Self::Keys => &future::KeysMethod,
183            Self::Values => &future::ValuesMethod,
184        }
185    }
186}
187
188impl ArrowMethod {
189    // This method is currently used at runtime to look up methods by &str name,
190    // but it could be hoisted parsing time, and then we'd store an ArrowMethod
191    // instead of a String for the method name in the AST.
192    pub(super) fn lookup(name: &str) -> Option<Self> {
193        let method_opt = match name {
194            "as" => Some(Self::As),
195            "echo" => Some(Self::Echo),
196            "map" => Some(Self::Map),
197            "eq" => Some(Self::Eq),
198            "match" => Some(Self::Match),
199            // As this case suggests, we can't necessarily provide a name()
200            // method for ArrowMethod (the opposite of lookup), because method
201            // implementations can be used under multiple names.
202            "matchIf" | "match_if" => Some(Self::MatchIf),
203            "typeof" => Some(Self::TypeOf),
204            "add" => Some(Self::Add),
205            "sub" => Some(Self::Sub),
206            "mul" => Some(Self::Mul),
207            "div" => Some(Self::Div),
208            "mod" => Some(Self::Mod),
209            "first" => Some(Self::First),
210            "last" => Some(Self::Last),
211            "slice" => Some(Self::Slice),
212            "size" => Some(Self::Size),
213            "has" => Some(Self::Has),
214            "get" => Some(Self::Get),
215            "keys" => Some(Self::Keys),
216            "values" => Some(Self::Values),
217            "entries" => Some(Self::Entries),
218            "not" => Some(Self::Not),
219            "or" => Some(Self::Or),
220            "and" => Some(Self::And),
221            "jsonStringify" => Some(Self::JsonStringify),
222            "joinNotNull" => Some(Self::JoinNotNull),
223            "filter" => Some(Self::Filter),
224            "find" => Some(Self::Find),
225            "gte" => Some(Self::Gte),
226            "lte" => Some(Self::Lte),
227            "ne" => Some(Self::Ne),
228            "gt" => Some(Self::Gt),
229            "lt" => Some(Self::Lt),
230            "in" => Some(Self::In),
231            "contains" => Some(Self::Contains),
232            "toString" => Some(Self::ToString),
233            "parseInt" => Some(Self::ParseInt),
234            _ => None,
235        };
236
237        match method_opt {
238            Some(method) if cfg!(test) || method.is_public() => Some(method),
239            _ => None,
240        }
241    }
242
243    pub(super) const fn is_public(&self) -> bool {
244        // This set controls which ->methods are exposed for use in connector
245        // schemas. Non-public methods are still implemented and tested, but
246        // will not be returned from lookup_arrow_method outside of tests.
247        matches!(
248            self,
249            Self::As
250                | Self::Echo
251                | Self::Map
252                | Self::Match
253                | Self::First
254                | Self::Last
255                | Self::Slice
256                | Self::Size
257                | Self::Entries
258                | Self::JsonStringify
259                | Self::JoinNotNull
260                | Self::Filter
261                | Self::Find
262                | Self::Gte
263                | Self::Lte
264                | Self::Eq
265                | Self::Ne
266                | Self::Or
267                | Self::And
268                | Self::Gt
269                | Self::Lt
270                | Self::Not
271                | Self::In
272                | Self::Contains
273                | Self::Get
274                | Self::ToString
275                | Self::ParseInt
276                | Self::Add
277                | Self::Sub
278                | Self::Mul
279                | Self::Div
280                | Self::Mod
281        )
282    }
283}