Skip to main content

appctl_plugin_sdk/
schema.rs

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}