Skip to main content

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