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, Default, PartialEq, Eq)]
29#[serde(rename_all = "snake_case")]
30pub enum Provenance {
31 #[default]
33 Inferred,
34 Declared,
36 Verified,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
41pub struct Action {
42 pub name: String,
43 #[serde(default)]
44 pub description: Option<String>,
45 pub verb: Verb,
46 pub transport: Transport,
47 #[serde(default)]
48 pub parameters: Vec<Field>,
49 pub safety: Safety,
50 #[serde(default)]
51 pub resource: Option<String>,
52 #[serde(default)]
54 pub provenance: Provenance,
55 #[serde(default)]
56 pub metadata: Map<String, Value>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60pub struct Field {
61 pub name: String,
62 #[serde(default)]
63 pub description: Option<String>,
64 pub field_type: FieldType,
65 #[serde(default)]
66 pub required: bool,
67 #[serde(default)]
68 pub location: Option<ParameterLocation>,
69 #[serde(default)]
70 pub default: Option<Value>,
71 #[serde(default)]
72 pub enum_values: Vec<Value>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
76#[serde(rename_all = "snake_case")]
77pub enum SyncSource {
78 Openapi,
79 Django,
80 Flask,
81 Db,
82 Url,
83 Mcp,
84 Rails,
85 Laravel,
86 Aspnet,
87 Strapi,
88 Supabase,
89 Plugin,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
93#[serde(rename_all = "snake_case")]
94pub enum Verb {
95 List,
96 Get,
97 Create,
98 Update,
99 Delete,
100 Custom,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
104#[serde(rename_all = "snake_case")]
105pub enum Safety {
106 ReadOnly,
107 Mutating,
108 Destructive,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
112#[serde(tag = "kind", rename_all = "snake_case")]
113pub enum Transport {
114 Http {
115 method: HttpMethod,
116 path: String,
117 #[serde(default)]
118 query: Vec<String>,
119 },
120 Sql {
121 database_kind: DatabaseKind,
122 table: String,
123 operation: SqlOperation,
124 #[serde(default)]
125 primary_key: Option<String>,
126 },
127 NoSql {
128 database_kind: DatabaseKind,
129 collection: String,
130 operation: NoSqlOperation,
131 #[serde(default)]
132 primary_key: Option<String>,
133 #[serde(default)]
134 secondary_key: Option<String>,
135 },
136 Form {
137 method: HttpMethod,
138 action: String,
139 },
140 Mcp {
141 server_url: String,
142 },
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
146#[serde(rename_all = "snake_case")]
147pub enum DatabaseKind {
148 Postgres,
149 Mysql,
150 Sqlite,
151 Mongodb,
152 Redis,
153 Firestore,
154 Dynamodb,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
158#[serde(rename_all = "snake_case")]
159pub enum SqlOperation {
160 Select,
161 GetByPk,
162 Insert,
163 UpdateByPk,
164 DeleteByPk,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
168#[serde(rename_all = "snake_case")]
169pub enum NoSqlOperation {
170 List,
171 GetByPk,
172 Insert,
173 UpdateByPk,
174 DeleteByPk,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
178#[serde(rename_all = "UPPERCASE")]
179pub enum HttpMethod {
180 GET,
181 POST,
182 PUT,
183 PATCH,
184 DELETE,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
188#[serde(tag = "kind", rename_all = "snake_case")]
189pub enum AuthStrategy {
190 None,
191 ApiKey {
192 header: String,
193 env_ref: String,
194 },
195 Bearer {
196 env_ref: String,
197 },
198 Basic {
199 username_ref: String,
200 password_ref: String,
201 },
202 Cookie {
203 #[serde(default)]
204 env_ref: Option<String>,
205 #[serde(default)]
206 session_file: Option<String>,
207 },
208 OAuth2 {
209 #[serde(default)]
210 provider: Option<String>,
211 client_id_ref: String,
212 #[serde(default)]
213 client_secret_ref: Option<String>,
214 auth_url: String,
215 token_url: String,
216 #[serde(default)]
217 scopes: Vec<String>,
218 #[serde(default = "default_redirect_port")]
219 redirect_port: u16,
220 },
221}
222
223fn default_redirect_port() -> u16 {
224 8421
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
228#[serde(rename_all = "snake_case")]
229pub enum FieldType {
230 String,
231 Integer,
232 Number,
233 Boolean,
234 Object,
235 Array,
236 DateTime,
237 Date,
238 Uuid,
239 Json,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
243#[serde(rename_all = "snake_case")]
244pub enum ParameterLocation {
245 Path,
246 Query,
247 Body,
248 Header,
249}
250
251impl Schema {
252 pub fn action(&self, name: &str) -> Option<&Action> {
253 self.resources
254 .iter()
255 .flat_map(|resource| resource.actions.iter())
256 .find(|action| action.name == name)
257 }
258}
259
260impl FieldType {
261 pub fn from_openapi_type(ty: Option<&str>, format: Option<&str>) -> Self {
262 match (ty.unwrap_or("string"), format.unwrap_or_default()) {
263 ("integer", _) => Self::Integer,
264 ("number", _) => Self::Number,
265 ("boolean", _) => Self::Boolean,
266 ("object", _) => Self::Object,
267 ("array", _) => Self::Array,
268 ("string", "date-time") => Self::DateTime,
269 ("string", "date") => Self::Date,
270 ("string", "uuid") => Self::Uuid,
271 ("string", _) => Self::String,
272 _ => Self::Json,
273 }
274 }
275}