Skip to main content

gqls/
model.rs

1//! The language-agnostic record model: every type, field, arg, enum value,
2//! and directive in a schema flattens to one [`SchemaRecord`]. Search and
3//! output operate only on these — they never touch a parser or a transport.
4
5use std::str::FromStr;
6
7use serde::Serialize;
8
9/// What a [`SchemaRecord`] describes. Root operation fields get their own
10/// kinds (`Query`/`Mutation`/`Subscription`) so ranking can float them to
11/// the top the way `rq` floats top-level definitions.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
13#[serde(rename_all = "snake_case")]
14pub enum Kind {
15    Object,
16    Interface,
17    Union,
18    Enum,
19    InputObject,
20    Scalar,
21    Directive,
22    Field,
23    InputField,
24    EnumValue,
25    Query,
26    Mutation,
27    Subscription,
28}
29
30impl Kind {
31    pub fn as_str(self) -> &'static str {
32        match self {
33            Kind::Object => "object",
34            Kind::Interface => "interface",
35            Kind::Union => "union",
36            Kind::Enum => "enum",
37            Kind::InputObject => "input_object",
38            Kind::Scalar => "scalar",
39            Kind::Directive => "directive",
40            Kind::Field => "field",
41            Kind::InputField => "input_field",
42            Kind::EnumValue => "enum_value",
43            Kind::Query => "query",
44            Kind::Mutation => "mutation",
45            Kind::Subscription => "subscription",
46        }
47    }
48
49    /// Static ranking bias by kind — roots and named types outrank leaf
50    /// args. (In `rq` this is the `kind`-weight table in `score.rs`.)
51    pub fn weight(self) -> i64 {
52        match self {
53            Kind::Query | Kind::Mutation | Kind::Subscription => 60,
54            Kind::Object | Kind::Interface | Kind::Union | Kind::Enum | Kind::InputObject
55            | Kind::Scalar => 40,
56            Kind::Directive => 30,
57            Kind::Field | Kind::InputField => 20,
58            Kind::EnumValue => 10,
59        }
60    }
61}
62
63impl FromStr for Kind {
64    type Err = anyhow::Error;
65
66    fn from_str(s: &str) -> Result<Self, Self::Err> {
67        Ok(match s.to_lowercase().as_str() {
68            "object" | "objects" | "type" | "types" => Kind::Object,
69            "interface" | "interfaces" => Kind::Interface,
70            "union" | "unions" => Kind::Union,
71            "enum" | "enums" => Kind::Enum,
72            "input" | "input_object" | "input_objects" | "inputobject" => Kind::InputObject,
73            "scalar" | "scalars" => Kind::Scalar,
74            "directive" | "directives" => Kind::Directive,
75            "field" | "fields" => Kind::Field,
76            "input_field" | "input_fields" | "inputfield" => Kind::InputField,
77            "enum_value" | "enum_values" | "enumvalue" => Kind::EnumValue,
78            "query" | "queries" => Kind::Query,
79            "mutation" | "mutations" => Kind::Mutation,
80            "subscription" | "subscriptions" => Kind::Subscription,
81            other => anyhow::bail!(
82                "unknown kind {other:?} — valid: object, interface, union, enum, \
83                 input_object, scalar, directive, field, input_field, enum_value, \
84                 query, mutation, subscription (plurals ok)"
85            ),
86        })
87    }
88}
89
90/// One searchable schema entity.
91#[derive(Debug, Clone, Serialize)]
92pub struct SchemaRecord {
93    /// Fully-qualified path, e.g. `User.email`, `Query.user`, `@deprecated`.
94    pub path: String,
95    /// The leaf name matched against — `email`, `user`, `User`.
96    pub name: String,
97    pub kind: Kind,
98    /// Enclosing type name for fields/args/enum values.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub parent: Option<String>,
101    /// Return/field/arg type, rendered like `[User!]!`.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub type_ref: Option<String>,
104    /// Argument signatures for a field, e.g. `["id: ID!", "first: Int"]`.
105    #[serde(skip_serializing_if = "Vec::is_empty")]
106    pub args: Vec<String>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub description: Option<String>,
109    /// Deprecation reason, if the entity is `@deprecated`.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub deprecated: Option<String>,
112    /// Applied directives, rendered like `@auth`.
113    #[serde(skip_serializing_if = "Vec::is_empty")]
114    pub directives: Vec<String>,
115}