@@ -5,6 +5,23 @@ use std::path::{Path, PathBuf};
55/// Default configuration embedded in the binary.
66pub const DEFAULT_CONFIG_TOML : & str = include_str ! ( "assets/config.toml" ) ;
77
8+ /// Error type for configuration loading failures.
9+ #[ derive( Debug , thiserror:: Error ) ]
10+ pub enum ConfigError {
11+ #[ error( "Failed to read config file '{path}': {source}" ) ]
12+ Io {
13+ path : PathBuf ,
14+ #[ source]
15+ source : std:: io:: Error ,
16+ } ,
17+ #[ error( "Failed to parse config file '{path}':\n {source}" ) ]
18+ Parse {
19+ path : PathBuf ,
20+ #[ source]
21+ source : toml:: de:: Error ,
22+ } ,
23+ }
24+
825/// Filenames to search for project-level configuration.
926const CONFIG_FILENAMES : & [ & str ] = & [ "flake-edit.toml" , ".flake-edit.toml" ] ;
1027
@@ -80,10 +97,39 @@ impl Config {
8097 /// 1. Project-level config (flake-edit.toml or .flake-edit.toml in current/parent dirs)
8198 /// 2. User-level config (~/.config/flake-edit/config.toml)
8299 /// 3. Default embedded config
83- pub fn load ( ) -> Self {
84- Self :: load_project_config ( )
85- . or_else ( Self :: load_user_config)
86- . unwrap_or_default ( )
100+ ///
101+ /// Returns an error if a config file exists but is malformed.
102+ pub fn load ( ) -> Result < Self , ConfigError > {
103+ if let Some ( path) = Self :: project_config_path ( ) {
104+ return Self :: try_load_from_file ( & path) ;
105+ }
106+ if let Some ( path) = Self :: user_config_path ( ) {
107+ return Self :: try_load_from_file ( & path) ;
108+ }
109+ Ok ( Self :: default ( ) )
110+ }
111+
112+ /// Load configuration from an explicitly specified path.
113+ ///
114+ /// Returns an error if the file doesn't exist or is malformed.
115+ /// If no path is specified, falls back to the default load order.
116+ pub fn load_from ( path : Option < & Path > ) -> Result < Self , ConfigError > {
117+ match path {
118+ Some ( p) => Self :: try_load_from_file ( p) ,
119+ None => Self :: load ( ) ,
120+ }
121+ }
122+
123+ /// Try to load config from a file, returning detailed errors on failure.
124+ fn try_load_from_file ( path : & Path ) -> Result < Self , ConfigError > {
125+ let content = std:: fs:: read_to_string ( path) . map_err ( |e| ConfigError :: Io {
126+ path : path. to_path_buf ( ) ,
127+ source : e,
128+ } ) ?;
129+ toml:: from_str ( & content) . map_err ( |e| ConfigError :: Parse {
130+ path : path. to_path_buf ( ) ,
131+ source : e,
132+ } )
87133 }
88134
89135 pub fn project_config_path ( ) -> Option < PathBuf > {
@@ -105,17 +151,6 @@ impl Config {
105151 Self :: xdg_config_dir ( )
106152 }
107153
108- fn load_project_config ( ) -> Option < Self > {
109- let path = Self :: project_config_path ( ) ?;
110- Self :: load_from_file ( & path)
111- }
112-
113- /// Load user-level config from XDG config directory.
114- fn load_user_config ( ) -> Option < Self > {
115- let path = Self :: user_config_path ( ) ?;
116- Self :: load_from_file ( & path)
117- }
118-
119154 fn find_config_in_ancestors ( start : & Path ) -> Option < PathBuf > {
120155 let mut current = start. to_path_buf ( ) ;
121156 loop {
@@ -131,17 +166,6 @@ impl Config {
131166 }
132167 None
133168 }
134-
135- fn load_from_file ( path : & Path ) -> Option < Self > {
136- let content = std:: fs:: read_to_string ( path) . ok ( ) ?;
137- match toml:: from_str ( & content) {
138- Ok ( config) => Some ( config) ,
139- Err ( e) => {
140- tracing:: warn!( "Failed to parse config at {}: {}" , path. display( ) , e) ;
141- None
142- }
143- }
144- }
145169}
146170
147171#[ cfg( test) ]
0 commit comments