kotoba_server/frontend/
api_ir.rs

1//! APIルートIR定義
2//!
3//! REST API、GraphQL、WebSocketなどのAPIエンドポイントを表現します。
4
5use kotoba_core::prelude::*;
6use crate::frontend::component_ir::ComponentIR;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10/// APIメソッド
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum ApiMethod {
13    GET,
14    POST,
15    PUT,
16    DELETE,
17    PATCH,
18    HEAD,
19    OPTIONS,
20    TRACE,
21    CONNECT,
22}
23
24impl std::fmt::Display for ApiMethod {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            ApiMethod::GET => write!(f, "GET"),
28            ApiMethod::POST => write!(f, "POST"),
29            ApiMethod::PUT => write!(f, "PUT"),
30            ApiMethod::DELETE => write!(f, "DELETE"),
31            ApiMethod::PATCH => write!(f, "PATCH"),
32            ApiMethod::HEAD => write!(f, "HEAD"),
33            ApiMethod::OPTIONS => write!(f, "OPTIONS"),
34            ApiMethod::TRACE => write!(f, "TRACE"),
35            ApiMethod::CONNECT => write!(f, "CONNECT"),
36        }
37    }
38}
39
40/// APIレスポンスフォーマット
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub enum ResponseFormat {
43    JSON,
44    XML,
45    HTML,
46    Text,
47    Binary,
48    GraphQL,
49}
50
51/// APIルートIR
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub struct ApiRouteIR {
54    pub path: String,
55    pub method: ApiMethod,
56    pub handler: ApiHandlerIR,
57    pub middlewares: Vec<String>, // ミドルウェア名
58    pub response_format: ResponseFormat,
59    pub parameters: ApiParameters,
60    pub metadata: ApiMetadata,
61}
62
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
64pub struct ApiHandlerIR {
65    pub function_name: String,
66    pub component: Option<ComponentIR>, // APIコンポーネント
67    pub is_async: bool,
68    pub timeout_ms: Option<u64>,
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct ApiParameters {
73    pub path_params: Vec<ApiParameter>,
74    pub query_params: Vec<ApiParameter>,
75    pub body_params: Option<ApiBodySchema>,
76    pub headers: Vec<ApiParameter>,
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub struct ApiParameter {
81    pub name: String,
82    pub param_type: ParameterType,
83    pub required: bool,
84    pub default_value: Option<Value>,
85    pub validation: Option<ValidationRules>,
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub enum ParameterType {
90    String,
91    Integer,
92    Float,
93    Boolean,
94    Array,
95    Object,
96    File,
97    Date,
98    DateTime,
99}
100
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub struct ValidationRules {
103    pub min_length: Option<usize>,
104    pub max_length: Option<usize>,
105    pub pattern: Option<String>,
106    pub min_value: Option<f64>,
107    pub max_value: Option<f64>,
108    pub allowed_values: Vec<Value>,
109}
110
111#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112pub struct ApiBodySchema {
113    pub content_type: String,
114    pub schema: Value, // JSON Schema または類似の構造
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub struct ApiMetadata {
119    pub description: Option<String>,
120    pub summary: Option<String>,
121    pub tags: Vec<String>,
122    pub deprecated: bool,
123    pub rate_limit: Option<RateLimit>,
124    pub cache: Option<CacheConfig>,
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub struct RateLimit {
129    pub requests: u32,
130    pub window_seconds: u64,
131    pub strategy: RateLimitStrategy,
132}
133
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135pub enum RateLimitStrategy {
136    FixedWindow,
137    SlidingWindow,
138    TokenBucket,
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct CacheConfig {
143    pub ttl_seconds: u64,
144    pub vary_by: Vec<String>, // キャッシュキー生成のためのパラメータ
145}
146
147/// APIレスポンスIR
148#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
149pub struct ApiResponseIR {
150    pub status_code: u16,
151    pub headers: Properties,
152    pub body: ApiResponseBody,
153    pub metadata: ResponseMetadata,
154}
155
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
157pub enum ApiResponseBody {
158    JSON(Value),
159    Text(String),
160    HTML(String),
161    Binary(Vec<u8>),
162    File { path: String, filename: String },
163    // Stream(Box<dyn std::io::Read>), // ストリーミングレスポンス用 - TODO: 実装予定
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub struct ResponseMetadata {
168    pub content_type: String,
169    pub content_length: Option<usize>,
170    pub cache_control: Option<String>,
171    pub etag: Option<String>,
172}
173
174/// データベースIR
175#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176pub struct DatabaseIR {
177    pub connection_string: String,
178    pub db_type: DatabaseType,
179    pub models: Vec<ModelIR>,
180    pub migrations: Vec<MigrationIR>,
181}
182
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub enum DatabaseType {
185    PostgreSQL,
186    MySQL,
187    SQLite,
188    MongoDB,
189    Redis,
190    Custom(String),
191}
192
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
194pub struct ModelIR {
195    pub name: String,
196    pub table_name: String,
197    pub fields: Vec<FieldIR>,
198    pub relationships: Vec<RelationshipIR>,
199    pub indexes: Vec<IndexIR>,
200}
201
202#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
203pub struct FieldIR {
204    pub name: String,
205    pub field_type: FieldType,
206    pub nullable: bool,
207    pub default_value: Option<Value>,
208    pub unique: bool,
209    pub primary_key: bool,
210}
211
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213pub enum FieldType {
214    String { max_length: Option<usize> },
215    Text,
216    Integer,
217    BigInt,
218    Float,
219    Double,
220    Decimal { precision: u32, scale: u32 },
221    Boolean,
222    Date,
223    DateTime,
224    Time,
225    UUID,
226    JSON,
227    Binary,
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
231pub struct RelationshipIR {
232    pub name: String,
233    pub target_model: String,
234    pub relationship_type: RelationshipType,
235    pub foreign_key: String,
236    pub on_delete: CascadeAction,
237    pub on_update: CascadeAction,
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
241pub enum RelationshipType {
242    OneToOne,
243    OneToMany,
244    ManyToMany,
245}
246
247#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
248pub enum CascadeAction {
249    Cascade,
250    Restrict,
251    SetNull,
252    SetDefault,
253    NoAction,
254}
255
256#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
257pub struct IndexIR {
258    pub name: String,
259    pub fields: Vec<String>,
260    pub unique: bool,
261    pub index_type: IndexType,
262}
263
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
265pub enum IndexType {
266    BTree,
267    Hash,
268    GIN,
269    GiST,
270    SPGiST,
271    BRIN,
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275pub struct MigrationIR {
276    pub version: String,
277    pub description: String,
278    pub up_sql: String,
279    pub down_sql: String,
280    pub dependencies: Vec<String>,
281}
282
283/// ミドルウェアIR
284#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
285pub struct MiddlewareIR {
286    pub name: String,
287    pub middleware_type: MiddlewareType,
288    pub config: Properties,
289    pub order: i32,
290}
291
292#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
293pub enum MiddlewareType {
294    Authentication,
295    Authorization,
296    CORS,
297    Compression,
298    CSRF,
299    Logging,
300    RateLimiting,
301    Session,
302    StaticFiles,
303    Custom(String),
304}
305
306/// WebSocket IR
307#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
308pub struct WebSocketIR {
309    pub path: String,
310    pub handler: WebSocketHandlerIR,
311    pub protocols: Vec<String>,
312    pub heartbeat_interval: Option<u64>,
313}
314
315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
316pub struct WebSocketHandlerIR {
317    pub on_connect: String,
318    pub on_message: String,
319    pub on_disconnect: String,
320    pub on_error: String,
321}
322
323/// GraphQL IR
324#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
325pub struct GraphQLIR {
326    pub schema: GraphQLSchemaIR,
327    pub resolvers: HashMap<String, GraphQLResolverIR>,
328    pub directives: Vec<GraphQLDirectiveIR>,
329}
330
331#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
332pub struct GraphQLSchemaIR {
333    pub query: Option<String>,
334    pub mutation: Option<String>,
335    pub subscription: Option<String>,
336    pub types: Vec<GraphQLTypeIR>,
337}
338
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
340pub struct GraphQLTypeIR {
341    pub name: String,
342    pub kind: GraphQLTypeKind,
343    pub fields: Vec<GraphQLFieldIR>,
344}
345
346#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
347pub enum GraphQLTypeKind {
348    Object,
349    Interface,
350    Union,
351    Enum,
352    InputObject,
353    Scalar,
354}
355
356#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
357pub struct GraphQLFieldIR {
358    pub name: String,
359    pub field_type: String,
360    pub args: Vec<GraphQLArgumentIR>,
361    pub resolver: Option<String>,
362}
363
364#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
365pub struct GraphQLArgumentIR {
366    pub name: String,
367    pub arg_type: String,
368    pub default_value: Option<Value>,
369}
370
371#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
372pub struct GraphQLResolverIR {
373    pub field_name: String,
374    pub function_name: String,
375    pub is_async: bool,
376}
377
378#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
379pub struct GraphQLDirectiveIR {
380    pub name: String,
381    pub locations: Vec<DirectiveLocation>,
382    pub args: Vec<GraphQLArgumentIR>,
383}
384
385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub enum DirectiveLocation {
387    Query,
388    Mutation,
389    Subscription,
390    Field,
391    FragmentDefinition,
392    FragmentSpread,
393    InlineFragment,
394    Schema,
395    Scalar,
396    Object,
397    FieldDefinition,
398    ArgumentDefinition,
399    Interface,
400    Union,
401    Enum,
402    EnumValue,
403    InputObject,
404    InputFieldDefinition,
405}
406
407/// Webフレームワーク設定IR
408#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
409pub struct WebFrameworkConfigIR {
410    pub server: ServerConfig,
411    pub database: Option<DatabaseIR>,
412    pub api_routes: Vec<ApiRouteIR>,
413    pub web_sockets: Vec<WebSocketIR>,
414    pub graph_ql: Option<GraphQLIR>,
415    pub middlewares: Vec<MiddlewareIR>,
416    pub static_files: Vec<StaticFilesConfig>,
417    pub authentication: Option<AuthConfig>,
418    pub session: Option<SessionConfig>,
419}
420
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
422pub struct ServerConfig {
423    pub host: String,
424    pub port: u16,
425    pub tls: Option<TLSConfig>,
426    pub workers: usize,
427    pub max_connections: usize,
428}
429
430#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
431pub struct TLSConfig {
432    pub cert_path: String,
433    pub key_path: String,
434    pub ca_path: Option<String>,
435}
436
437#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
438pub struct StaticFilesConfig {
439    pub route: String,
440    pub directory: String,
441    pub cache_control: Option<String>,
442    pub gzip: bool,
443}
444
445#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
446pub struct AuthConfig {
447    pub provider: AuthProvider,
448    pub config: Properties,
449    pub jwt_secret: Option<String>,
450    pub session_timeout: u64,
451}
452
453#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
454pub enum AuthProvider {
455    Local,
456    OAuth2,
457    LDAP,
458    SAML,
459    Custom(String),
460}
461
462#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
463pub struct SessionConfig {
464    pub store: SessionStore,
465    pub cookie_name: String,
466    pub secure: bool,
467    pub http_only: bool,
468    pub same_site: SameSitePolicy,
469}
470
471#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
472pub enum SessionStore {
473    Memory,
474    Redis,
475    Database,
476    File,
477}
478
479#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
480pub enum SameSitePolicy {
481    Strict,
482    Lax,
483    None,
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489
490    #[test]
491    fn test_api_route_creation() {
492        let route = ApiRouteIR {
493            path: "/api/users".to_string(),
494            method: ApiMethod::GET,
495            handler: ApiHandlerIR {
496                function_name: "getUsers".to_string(),
497                component: None,
498                is_async: true,
499                timeout_ms: Some(5000),
500            },
501            middlewares: vec!["auth".to_string(), "cors".to_string()],
502            response_format: ResponseFormat::JSON,
503            parameters: ApiParameters {
504                path_params: Vec::new(),
505                query_params: vec![ApiParameter {
506                    name: "limit".to_string(),
507                    param_type: ParameterType::Integer,
508                    required: false,
509                    default_value: Some(Value::Int(10)),
510                    validation: Some(ValidationRules {
511                        min_length: None,
512                        max_length: None,
513                        pattern: None,
514                        min_value: Some(1.0),
515                        max_value: Some(100.0),
516                        allowed_values: Vec::new(),
517                    }),
518                }],
519                body_params: None,
520                headers: Vec::new(),
521            },
522            metadata: ApiMetadata {
523                description: Some("Get users list".to_string()),
524                summary: Some("Users API".to_string()),
525                tags: vec!["users".to_string()],
526                deprecated: false,
527                rate_limit: Some(RateLimit {
528                    requests: 100,
529                    window_seconds: 60,
530                    strategy: RateLimitStrategy::SlidingWindow,
531                }),
532                cache: Some(CacheConfig {
533                    ttl_seconds: 300,
534                    vary_by: vec!["user_id".to_string()],
535                }),
536            },
537        };
538
539        assert_eq!(route.path, "/api/users");
540        assert_eq!(route.method, ApiMethod::GET);
541        assert_eq!(route.handler.function_name, "getUsers");
542        assert!(route.handler.is_async);
543    }
544
545    #[test]
546    fn test_database_model_creation() {
547        let model = ModelIR {
548            name: "User".to_string(),
549            table_name: "users".to_string(),
550            fields: vec![
551                FieldIR {
552                    name: "id".to_string(),
553                    field_type: FieldType::UUID,
554                    nullable: false,
555                    default_value: None,
556                    unique: true,
557                    primary_key: true,
558                },
559                FieldIR {
560                    name: "email".to_string(),
561                    field_type: FieldType::String { max_length: Some(255) },
562                    nullable: false,
563                    default_value: None,
564                    unique: true,
565                    primary_key: false,
566                },
567                FieldIR {
568                    name: "created_at".to_string(),
569                    field_type: FieldType::DateTime,
570                    nullable: false,
571                    default_value: Some(Value::String("NOW()".to_string())),
572                    unique: false,
573                    primary_key: false,
574                },
575            ],
576            relationships: vec![
577                RelationshipIR {
578                    name: "posts".to_string(),
579                    target_model: "Post".to_string(),
580                    relationship_type: RelationshipType::OneToMany,
581                    foreign_key: "user_id".to_string(),
582                    on_delete: CascadeAction::Cascade,
583                    on_update: CascadeAction::NoAction,
584                },
585            ],
586            indexes: vec![
587                IndexIR {
588                    name: "idx_users_email".to_string(),
589                    fields: vec!["email".to_string()],
590                    unique: true,
591                    index_type: IndexType::BTree,
592                },
593            ],
594        };
595
596        assert_eq!(model.name, "User");
597        assert_eq!(model.table_name, "users");
598        assert_eq!(model.fields.len(), 3);
599        assert_eq!(model.relationships.len(), 1);
600        assert_eq!(model.indexes.len(), 1);
601    }
602
603    #[test]
604    fn test_middleware_creation() {
605        let middleware = MiddlewareIR {
606            name: "cors".to_string(),
607            middleware_type: MiddlewareType::CORS,
608            config: {
609                let mut props = Properties::new();
610                props.insert("allowed_origins".to_string(), Value::String("*".to_string()));
611                props.insert("allowed_methods".to_string(), Value::String("GET,POST,PUT,DELETE".to_string()));
612                props
613            },
614            order: 1,
615        };
616
617        assert_eq!(middleware.name, "cors");
618        assert_eq!(middleware.middleware_type, MiddlewareType::CORS);
619        assert_eq!(middleware.order, 1);
620    }
621}