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