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 Db,
81 Url,
82 Mcp,
83 Rails,
84 Laravel,
85 Aspnet,
86 Strapi,
87 Supabase,
88 Plugin,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
92#[serde(rename_all = "snake_case")]
93pub enum Verb {
94 List,
95 Get,
96 Create,
97 Update,
98 Delete,
99 Custom,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
103#[serde(rename_all = "snake_case")]
104pub enum Safety {
105 ReadOnly,
106 Mutating,
107 Destructive,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
111#[serde(tag = "kind", rename_all = "snake_case")]
112pub enum Transport {
113 Http {
114 method: HttpMethod,
115 path: String,
116 #[serde(default)]
117 query: Vec<String>,
118 },
119 Sql {
120 database_kind: DatabaseKind,
121 table: String,
122 operation: SqlOperation,
123 #[serde(default)]
124 primary_key: Option<String>,
125 },
126 Form {
127 method: HttpMethod,
128 action: String,
129 },
130 Mcp {
131 server_url: String,
132 },
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
136#[serde(rename_all = "snake_case")]
137pub enum DatabaseKind {
138 Postgres,
139 Mysql,
140 Sqlite,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
144#[serde(rename_all = "snake_case")]
145pub enum SqlOperation {
146 Select,
147 GetByPk,
148 Insert,
149 UpdateByPk,
150 DeleteByPk,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
154#[serde(rename_all = "UPPERCASE")]
155pub enum HttpMethod {
156 GET,
157 POST,
158 PUT,
159 PATCH,
160 DELETE,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
164#[serde(tag = "kind", rename_all = "snake_case")]
165pub enum AuthStrategy {
166 None,
167 ApiKey {
168 header: String,
169 env_ref: String,
170 },
171 Bearer {
172 env_ref: String,
173 },
174 Basic {
175 username_ref: String,
176 password_ref: String,
177 },
178 Cookie {
179 #[serde(default)]
180 env_ref: Option<String>,
181 #[serde(default)]
182 session_file: Option<String>,
183 },
184 OAuth2 {
185 #[serde(default)]
186 provider: Option<String>,
187 client_id_ref: String,
188 #[serde(default)]
189 client_secret_ref: Option<String>,
190 auth_url: String,
191 token_url: String,
192 #[serde(default)]
193 scopes: Vec<String>,
194 #[serde(default = "default_redirect_port")]
195 redirect_port: u16,
196 },
197}
198
199fn default_redirect_port() -> u16 {
200 8421
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
204#[serde(rename_all = "snake_case")]
205pub enum FieldType {
206 String,
207 Integer,
208 Number,
209 Boolean,
210 Object,
211 Array,
212 DateTime,
213 Date,
214 Uuid,
215 Json,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
219#[serde(rename_all = "snake_case")]
220pub enum ParameterLocation {
221 Path,
222 Query,
223 Body,
224 Header,
225}
226
227impl Schema {
228 pub fn action(&self, name: &str) -> Option<&Action> {
229 self.resources
230 .iter()
231 .flat_map(|resource| resource.actions.iter())
232 .find(|action| action.name == name)
233 }
234}
235
236impl FieldType {
237 pub fn from_openapi_type(ty: Option<&str>, format: Option<&str>) -> Self {
238 match (ty.unwrap_or("string"), format.unwrap_or_default()) {
239 ("integer", _) => Self::Integer,
240 ("number", _) => Self::Number,
241 ("boolean", _) => Self::Boolean,
242 ("object", _) => Self::Object,
243 ("array", _) => Self::Array,
244 ("string", "date-time") => Self::DateTime,
245 ("string", "date") => Self::Date,
246 ("string", "uuid") => Self::Uuid,
247 ("string", _) => Self::String,
248 _ => Self::Json,
249 }
250 }
251}