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}
141
142#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
143#[serde(rename_all = "snake_case")]
144pub enum SqlOperation {
145 Select,
146 GetByPk,
147 Insert,
148 UpdateByPk,
149 DeleteByPk,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
153#[serde(rename_all = "UPPERCASE")]
154pub enum HttpMethod {
155 GET,
156 POST,
157 PUT,
158 PATCH,
159 DELETE,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
163#[serde(tag = "kind", rename_all = "snake_case")]
164pub enum AuthStrategy {
165 None,
166 ApiKey {
167 header: String,
168 env_ref: String,
169 },
170 Bearer {
171 env_ref: String,
172 },
173 Basic {
174 username_ref: String,
175 password_ref: String,
176 },
177 Cookie {
178 #[serde(default)]
179 env_ref: Option<String>,
180 #[serde(default)]
181 session_file: Option<String>,
182 },
183 OAuth2 {
184 #[serde(default)]
185 provider: Option<String>,
186 client_id_ref: String,
187 #[serde(default)]
188 client_secret_ref: Option<String>,
189 auth_url: String,
190 token_url: String,
191 #[serde(default)]
192 scopes: Vec<String>,
193 #[serde(default = "default_redirect_port")]
194 redirect_port: u16,
195 },
196}
197
198fn default_redirect_port() -> u16 {
199 8421
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
203#[serde(rename_all = "snake_case")]
204pub enum FieldType {
205 String,
206 Integer,
207 Number,
208 Boolean,
209 Object,
210 Array,
211 DateTime,
212 Date,
213 Uuid,
214 Json,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
218#[serde(rename_all = "snake_case")]
219pub enum ParameterLocation {
220 Path,
221 Query,
222 Body,
223 Header,
224}
225
226impl Schema {
227 pub fn action(&self, name: &str) -> Option<&Action> {
228 self.resources
229 .iter()
230 .flat_map(|resource| resource.actions.iter())
231 .find(|action| action.name == name)
232 }
233}
234
235impl FieldType {
236 pub fn from_openapi_type(ty: Option<&str>, format: Option<&str>) -> Self {
237 match (ty.unwrap_or("string"), format.unwrap_or_default()) {
238 ("integer", _) => Self::Integer,
239 ("number", _) => Self::Number,
240 ("boolean", _) => Self::Boolean,
241 ("object", _) => Self::Object,
242 ("array", _) => Self::Array,
243 ("string", "date-time") => Self::DateTime,
244 ("string", "date") => Self::Date,
245 ("string", "uuid") => Self::Uuid,
246 ("string", _) => Self::String,
247 _ => Self::Json,
248 }
249 }
250}