graphql_federated_graph/federated_graph/directives/
link.rs

1use std::collections::HashMap;
2
3use cynic_parser_deser::{ConstDeserializer, ValueDeserialize};
4use serde::Deserialize;
5
6/// directive @link(
7///   url: String!,
8///   as: String,
9///   import: [Import],
10///   for: Purpose)
11///   repeatable on SCHEMA
12///
13/// Source: https://specs.apollo.dev/link/v1.0/
14#[derive(Debug)]
15pub struct LinkDirective<'a> {
16    pub url: &'a str,
17    pub r#as: Option<&'a str>,
18    pub import: Option<Vec<Import<'a>>>,
19    pub r#for: Option<Purpose>,
20}
21
22impl<'a> ValueDeserialize<'a> for LinkDirective<'a> {
23    fn deserialize(input: cynic_parser_deser::DeserValue<'a>) -> Result<Self, cynic_parser_deser::Error> {
24        let fields = input
25            .as_object()
26            .ok_or_else(|| cynic_parser_deser::Error::custom("Bad link directive", input.span()))?;
27
28        let mut url = None;
29        let mut r#as = None;
30        let mut import = None;
31        let mut r#for = None;
32
33        for field in fields {
34            match field.name() {
35                "url" => {
36                    url = Some(field.value().as_str().ok_or_else(|| {
37                        cynic_parser_deser::Error::custom("Bad `url` argument in `@link` directive", field.name_span())
38                    })?)
39                }
40                "as" => {
41                    r#as = Some(field.value().as_str().ok_or_else(|| {
42                        cynic_parser_deser::Error::custom("Bad `as` argument in `@link` directive", field.name_span())
43                    })?)
44                }
45                "for" => r#for = Some(field.value().deserialize()?),
46                "import" => import = Some(field.value().deserialize()?),
47                other => {
48                    return Err(cynic_parser_deser::Error::custom(
49                        format!("Unknown argument `{}` in `@link` directive", other),
50                        field.name_span(),
51                    ));
52                }
53            }
54        }
55
56        let Some(url) = url else {
57            return Err(cynic_parser_deser::Error::custom(
58                "Missing `url` argument in `@link` directive",
59                input.span(),
60            ));
61        };
62
63        Ok(LinkDirective {
64            url,
65            r#as,
66            import,
67            r#for,
68        })
69    }
70}
71
72#[derive(Debug, Deserialize)]
73pub enum Purpose {
74    Security,
75    Execution,
76}
77
78impl<'a> ValueDeserialize<'a> for Purpose {
79    fn deserialize(input: cynic_parser_deser::DeserValue<'a>) -> Result<Self, cynic_parser_deser::Error> {
80        let str: &str = input.deserialize()?;
81
82        match str {
83            "SECURITY" => Ok(Purpose::Security),
84            "EXECUTION" => Ok(Purpose::Execution),
85            _ => Err(cynic_parser_deser::Error::custom("Bad purpose", input.span())),
86        }
87    }
88}
89
90#[derive(Debug)]
91pub enum Import<'a> {
92    String(&'a str),
93    Qualified(QualifiedImport<'a>),
94}
95
96#[derive(Debug)]
97pub struct QualifiedImport<'a> {
98    pub name: &'a str,
99    pub r#as: Option<&'a str>,
100}
101
102impl<'a> ValueDeserialize<'a> for QualifiedImport<'a> {
103    fn deserialize(input: cynic_parser_deser::DeserValue<'a>) -> Result<Self, cynic_parser_deser::Error> {
104        let Some(object) = input.as_object() else {
105            return Err(cynic_parser_deser::Error::Custom {
106                text: "Bad import".to_owned(),
107                span: input.span(),
108            });
109        };
110
111        let mut fields: HashMap<&str, _> = object.fields().map(|field| (field.name(), field)).collect();
112
113        if fields.len() > 2 {
114            return Err(cynic_parser_deser::Error::Custom {
115                text: "Bad import".to_owned(),
116                span: input.span(),
117            });
118        }
119
120        let Some(name) = fields.remove("name").and_then(|field| field.value().as_str()) else {
121            return Err(cynic_parser_deser::Error::Custom {
122                text: "Bad import".to_owned(),
123                span: input.span(),
124            });
125        };
126
127        let r#as = fields
128            .remove("as")
129            .map(|alias| {
130                alias
131                    .value()
132                    .as_str()
133                    .ok_or_else(|| cynic_parser_deser::Error::custom("Bad import", input.span()))
134            })
135            .transpose()?;
136
137        Ok(QualifiedImport { name, r#as })
138    }
139}
140
141impl<'a> ValueDeserialize<'a> for Import<'a> {
142    fn deserialize(input: cynic_parser_deser::DeserValue<'a>) -> Result<Self, cynic_parser_deser::Error> {
143        if let Some(string) = input.as_str() {
144            return Ok(Import::String(string));
145        }
146
147        if input.as_object().is_some() {
148            return Ok(Import::Qualified(input.deserialize()?));
149        }
150
151        Err(cynic_parser_deser::Error::custom("Bad import", input.span()))
152    }
153}