Skip to main content

endpoint_libs/model/
endpoint.rs

1use crate::model::{Field, Type};
2use convert_case::{Case, Casing};
3use eyre::{ContextCompat, Result};
4use serde::de::{Error, Unexpected};
5use serde::ser::SerializeStruct;
6use serde::*;
7use std::fmt::Write;
8
9/// `EndpointSchema` is a struct that represents a single endpoint in the API.
10#[derive(Debug, Serialize, Deserialize, Default, Clone)]
11pub struct EndpointSchema {
12    /// The name of the endpoint (e.g. `UserListSymbols`)
13    pub name: String,
14
15    /// The method code of the endpoint (e.g. `10020`)
16    pub code: u32,
17
18    /// A list of parameters that the endpoint accepts (e.g. "symbol" of type `String`)
19    pub parameters: Vec<Field>,
20
21    /// A list of fields that the endpoint returns
22    pub returns: Vec<Field>,
23
24    /// The type of the stream response (if any)
25    #[serde(default)]
26    pub stream_response: Option<Type>,
27
28    /// A description of the endpoint added by `with_description` method
29    #[serde(default)]
30    pub description: String,
31
32    /// The JSON schema of the endpoint (`Default::default()`)
33    #[serde(default)]
34    pub json_schema: serde_json::Value,
35
36    // Allowed roles for this endpoint ["EnumRole::EnumVariant"]
37    pub roles: Vec<String>,
38
39    /// Public error variants that handlers may return for this endpoint.
40    #[serde(default)]
41    pub errors: Vec<EndpointErrorSchema>,
42}
43
44impl EndpointSchema {
45    /// Creates a new `EndpointSchema` with the given name, method code, parameters and returns.
46    pub fn new(
47        name: impl Into<String>,
48        code: u32,
49        parameters: Vec<Field>,
50        returns: Vec<Field>,
51    ) -> Self {
52        Self {
53            name: name.into(),
54            code,
55            parameters,
56            returns,
57            stream_response: None,
58            description: "".to_string(),
59            json_schema: Default::default(),
60            roles: Vec::new(),
61            errors: Vec::new(),
62        }
63    }
64
65    /// Adds a stream response type field to the endpoint.
66    pub fn with_stream_response_type(mut self, stream_response: Type) -> Self {
67        self.stream_response = Some(stream_response);
68        self
69    }
70
71    /// Adds a description field to the endpoint.
72    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
73        self.description = desc.into();
74        self
75    }
76
77    /// Adds allowed roles to the endpoint.
78    pub fn with_roles(mut self, roles: Vec<String>) -> Self {
79        self.roles = roles;
80        self
81    }
82
83    /// Adds public error variants to the endpoint.
84    pub fn with_errors(mut self, errors: Vec<EndpointErrorSchema>) -> Self {
85        self.errors = errors;
86        self
87    }
88}
89
90#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord)]
91pub struct EndpointErrorSchema {
92    pub name: String,
93    pub code: EndpointErrorCodeRef,
94    #[serde(default)]
95    pub message: String,
96    #[serde(default)]
97    pub fields: Vec<Field>,
98}
99
100#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Eq, Ord)]
101pub struct EndpointErrorCodeRef {
102    pub ty: Type,
103    pub variant: String,
104}
105
106impl EndpointErrorCodeRef {
107    pub const ENUM_NAME: &'static str = "ErrorCode";
108
109    pub fn new(variant: impl Into<String>) -> Self {
110        Self {
111            ty: Type::enum_ref(Self::ENUM_NAME, true),
112            variant: variant.into(),
113        }
114    }
115
116    pub fn variant(&self) -> &str {
117        &self.variant
118    }
119
120    pub fn path(&self) -> String {
121        format!("{}::{}", Self::ENUM_NAME, self.variant)
122    }
123
124    fn validate_ty(ty: Type) -> std::result::Result<Type, String> {
125        match &ty {
126            Type::EnumRef { name, .. } if name == Self::ENUM_NAME => Ok(ty),
127            Type::EnumRef { name, .. } => {
128                Err(format!("expected {} enum ref, got {name}", Self::ENUM_NAME))
129            }
130            _ => Err(format!("expected {} enum ref", Self::ENUM_NAME)),
131        }
132    }
133
134    fn parse_path(path: &str) -> std::result::Result<Self, String> {
135        let (enum_name, variant) = path
136            .split_once("::")
137            .ok_or_else(|| format!("expected {}::Variant", Self::ENUM_NAME))?;
138
139        if enum_name != Self::ENUM_NAME {
140            return Err(format!(
141                "expected {} enum path, got {enum_name}",
142                Self::ENUM_NAME
143            ));
144        }
145
146        if variant.is_empty() || variant.contains("::") {
147            return Err(format!("expected {}::Variant", Self::ENUM_NAME));
148        }
149
150        Ok(Self::new(variant))
151    }
152}
153
154impl std::fmt::Display for EndpointErrorCodeRef {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        f.write_str(&self.path())
157    }
158}
159
160impl Serialize for EndpointErrorCodeRef {
161    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
162    where
163        S: Serializer,
164    {
165        let mut state = serializer.serialize_struct("EndpointErrorCodeRef", 2)?;
166        state.serialize_field("ty", &self.ty)?;
167        state.serialize_field("variant", &self.variant)?;
168        state.end()
169    }
170}
171
172impl<'de> Deserialize<'de> for EndpointErrorCodeRef {
173    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
174    where
175        D: Deserializer<'de>,
176    {
177        #[derive(Deserialize)]
178        #[serde(untagged)]
179        enum Helper {
180            Path(String),
181            Structured { ty: Type, variant: String },
182        }
183
184        match Helper::deserialize(deserializer)? {
185            Helper::Path(path) => Self::parse_path(&path)
186                .map_err(|err| D::Error::invalid_value(Unexpected::Str(&path), &err.as_str())),
187            Helper::Structured { ty, variant } => {
188                let ty = Self::validate_ty(ty).map_err(D::Error::custom)?;
189                Ok(Self { ty, variant })
190            }
191        }
192    }
193}
194
195pub fn encode_header<T: Serialize>(v: T, schema: EndpointSchema) -> Result<String> {
196    let mut s = String::new();
197    write!(s, "0{}", schema.name.to_ascii_lowercase())?;
198    let v = serde_json::to_value(&v)?;
199
200    for (i, f) in schema.parameters.iter().enumerate() {
201        let key = f.name.to_case(Case::Camel);
202        let value = v.get(&key).with_context(|| format!("key: {key}"))?;
203        if value.is_null() {
204            continue;
205        }
206        write!(
207            s,
208            ", {}{}",
209            i + 1,
210            urlencoding::encode(&value.to_string().replace('\"', ""))
211        )?;
212    }
213    Ok(s)
214}