1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use serde_json::{Map, Value};
4
5#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
6pub struct Schema {
7 pub source: SyncSource,
8 #[serde(default)]
9 pub base_url: Option<String>,
10 pub auth: AuthStrategy,
11 #[serde(default)]
12 pub resources: Vec<Resource>,
13 #[serde(default)]
14 pub metadata: Map<String, Value>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
18pub struct Resource {
19 pub name: String,
20 #[serde(default)]
21 pub description: Option<String>,
22 #[serde(default)]
23 pub fields: Vec<Field>,
24 #[serde(default)]
25 pub actions: Vec<Action>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
29pub struct Action {
30 pub name: String,
31 #[serde(default)]
32 pub description: Option<String>,
33 pub verb: Verb,
34 pub transport: Transport,
35 #[serde(default)]
36 pub parameters: Vec<Field>,
37 pub safety: Safety,
38 #[serde(default)]
39 pub resource: Option<String>,
40 #[serde(default)]
41 pub metadata: Map<String, Value>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
45pub struct Field {
46 pub name: String,
47 #[serde(default)]
48 pub description: Option<String>,
49 pub field_type: FieldType,
50 #[serde(default)]
51 pub required: bool,
52 #[serde(default)]
53 pub location: Option<ParameterLocation>,
54 #[serde(default)]
55 pub default: Option<Value>,
56 #[serde(default)]
57 pub enum_values: Vec<Value>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
61#[serde(rename_all = "snake_case")]
62pub enum SyncSource {
63 Openapi,
64 Django,
65 Db,
66 Url,
67 Mcp,
68 Rails,
69 Laravel,
70 Aspnet,
71 Strapi,
72 Supabase,
73 Plugin,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
77#[serde(rename_all = "snake_case")]
78pub enum Verb {
79 List,
80 Get,
81 Create,
82 Update,
83 Delete,
84 Custom,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
88#[serde(rename_all = "snake_case")]
89pub enum Safety {
90 ReadOnly,
91 Mutating,
92 Destructive,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
96#[serde(tag = "kind", rename_all = "snake_case")]
97pub enum Transport {
98 Http {
99 method: HttpMethod,
100 path: String,
101 #[serde(default)]
102 query: Vec<String>,
103 },
104 Sql {
105 database_kind: DatabaseKind,
106 table: String,
107 operation: SqlOperation,
108 #[serde(default)]
109 primary_key: Option<String>,
110 },
111 Form {
112 method: HttpMethod,
113 action: String,
114 },
115 Mcp {
116 server_url: String,
117 },
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
121#[serde(rename_all = "snake_case")]
122pub enum DatabaseKind {
123 Postgres,
124 Mysql,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
128#[serde(rename_all = "snake_case")]
129pub enum SqlOperation {
130 Select,
131 GetByPk,
132 Insert,
133 UpdateByPk,
134 DeleteByPk,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
138#[serde(rename_all = "UPPERCASE")]
139pub enum HttpMethod {
140 GET,
141 POST,
142 PUT,
143 PATCH,
144 DELETE,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
148#[serde(tag = "kind", rename_all = "snake_case")]
149pub enum AuthStrategy {
150 None,
151 ApiKey {
152 header: String,
153 env_ref: String,
154 },
155 Bearer {
156 env_ref: String,
157 },
158 Basic {
159 username_ref: String,
160 password_ref: String,
161 },
162 Cookie {
163 #[serde(default)]
164 env_ref: Option<String>,
165 #[serde(default)]
166 session_file: Option<String>,
167 },
168 OAuth2 {
169 #[serde(default)]
170 provider: Option<String>,
171 client_id_ref: String,
172 #[serde(default)]
173 client_secret_ref: Option<String>,
174 auth_url: String,
175 token_url: String,
176 #[serde(default)]
177 scopes: Vec<String>,
178 #[serde(default = "default_redirect_port")]
179 redirect_port: u16,
180 },
181}
182
183fn default_redirect_port() -> u16 {
184 8421
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
188#[serde(rename_all = "snake_case")]
189pub enum FieldType {
190 String,
191 Integer,
192 Number,
193 Boolean,
194 Object,
195 Array,
196 DateTime,
197 Date,
198 Uuid,
199 Json,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
203#[serde(rename_all = "snake_case")]
204pub enum ParameterLocation {
205 Path,
206 Query,
207 Body,
208 Header,
209}
210
211impl Schema {
212 pub fn action(&self, name: &str) -> Option<&Action> {
213 self.resources
214 .iter()
215 .flat_map(|resource| resource.actions.iter())
216 .find(|action| action.name == name)
217 }
218}
219
220impl FieldType {
221 pub fn from_openapi_type(ty: Option<&str>, format: Option<&str>) -> Self {
222 match (ty.unwrap_or("string"), format.unwrap_or_default()) {
223 ("integer", _) => Self::Integer,
224 ("number", _) => Self::Number,
225 ("boolean", _) => Self::Boolean,
226 ("object", _) => Self::Object,
227 ("array", _) => Self::Array,
228 ("string", "date-time") => Self::DateTime,
229 ("string", "date") => Self::Date,
230 ("string", "uuid") => Self::Uuid,
231 ("string", _) => Self::String,
232 _ => Self::Json,
233 }
234 }
235}