Coverage for src/lexigram/web/config/api_docs.py: 70%

30 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 04:37 +0800

1"""API documentation and static file configuration.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from typing import ClassVar 

7 

8from lexigram.config import BaseConfig 

9from lexigram.validation import ConfigDict, Field 

10 

11 

12@dataclass(init=False) 

13class APIDocsConfig(BaseConfig): 

14 """API documentation configuration. 

15 

16 Automatically configures CSP directives for API documentation endpoints 

17 (/docs for Swagger UI, /redoc for ReDoc) when enabled. 

18 """ 

19 

20 model_config: ClassVar[ConfigDict] = ConfigDict(extra="ignore") 

21 

22 enabled: bool = Field( 

23 default=True, 

24 description=( 

25 "Enable API documentation endpoints (/docs, /redoc) and auto-configure " 

26 "CSP for their CDN assets" 

27 ), 

28 ) 

29 provider: str = Field( 

30 default="both", 

31 description="Documentation provider: 'swagger', 'redoc', or 'both'", 

32 ) 

33 

34 # Domains needed for API docs 

35 SWAGGER_DOMAINS: ClassVar[dict[str, set[str]]] = { 

36 "script-src": {"https://unpkg.com"}, 

37 "script-src-elem": {"https://unpkg.com"}, # For <script src> elements 

38 "style-src": {"https://unpkg.com"}, 

39 "style-src-elem": {"https://unpkg.com"}, # For <link rel=stylesheet> elements 

40 } 

41 

42 REDOC_DOMAINS: ClassVar[dict[str, set[str]]] = { 

43 "script-src": {"https://cdn.redoc.ly"}, 

44 "script-src-elem": {"https://cdn.redoc.ly"}, # For <script src> elements 

45 "style-src": {"https://fonts.googleapis.com"}, 

46 "style-src-elem": { 

47 "https://fonts.googleapis.com" 

48 }, # For <link rel=stylesheet> elements 

49 "font-src": {"https://fonts.gstatic.com"}, 

50 "worker-src": {"blob:"}, 

51 } 

52 

53 def get_required_domains(self) -> dict[str, set[str]]: 

54 """Get required CSP domains based on provider setting.""" 

55 result: dict[str, set[str]] = {} 

56 

57 # Determine which providers to include 

58 providers = {"swagger", "redoc"} if self.provider == "both" else {self.provider} 

59 

60 if "swagger" in providers: 

61 for key, values in self.SWAGGER_DOMAINS.items(): 

62 result.setdefault(key, set()).update(values) 

63 

64 if "redoc" in providers: 

65 for key, values in self.REDOC_DOMAINS.items(): 

66 result.setdefault(key, set()).update(values) 

67 

68 return result 

69 

70 

71@dataclass(init=False) 

72class StaticFileConfig(BaseConfig): 

73 """Static file serving configuration.""" 

74 

75 model_config: ClassVar[ConfigDict] = ConfigDict(extra="ignore") 

76 

77 enabled: bool = Field(default=False, description="Enable static file serving") 

78 directory: str = Field(default="static", description="Directory to serve") 

79 prefix: str = Field(default="/static", description="URL prefix for static files") 

80 html: bool = Field(default=False, description="Serve HTML files (SPA mode)") 

81 

82 

83__all__ = [ 

84 "APIDocsConfig", 

85 "StaticFileConfig", 

86]