2026-09-06 21:03:45 +00:00
package configkit
import (
"bytes"
"errors"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
yamladapter "gitea.maximumdirect.net/eric/ConfigKit/internal/yaml"
)
func activeProfile ( name string ) bool {
return name != "" && name != "default"
}
func validProfileName ( name string ) bool {
if ! activeProfile ( name ) {
return true
}
for _ , character := range name {
if ( character >= 'a' && character <= 'z' ) ||
( character >= 'A' && character <= 'Z' ) ||
( character >= '0' && character <= '9' ) || character == '_' || character == '-' {
continue
}
return false
}
return true
}
func loadProfileDocument ( source Source , context * FilesystemContext , main parsedSource , base * composedDocument , sources [ ] SourceInfo , name string , rule * VersionRule , limits importLimits ) ( * composedDocument , [ ] SourceInfo , [ ] Diagnostic ) {
rootPath , _ , _ , diagnostic := importFilesystem ( source , context , main )
if diagnostic != nil {
return nil , cloneSourceInfos ( sources ) , [ ] Diagnostic { * diagnostic }
}
profilePath := filepath . Join ( rootPath , "profiles" , name + ".yml" )
canonical , err := canonicalExistingPath ( profilePath )
if err != nil {
code := CodeProfileInvalid
message := "selected profile could not be resolved"
if errors . Is ( err , fs . ErrNotExist ) {
code , message = CodeProfileMissing , "selected profile does not exist"
} else if errors . Is ( err , fs . ErrPermission ) {
code , message = CodeImportPermission , "selected profile cannot be accessed"
}
return nil , cloneSourceInfos ( sources ) , [ ] Diagnostic { importDiagnostic ( code , message , nil ) }
}
if ! containedPath ( rootPath , canonical ) {
return nil , cloneSourceInfos ( sources ) , [ ] Diagnostic { importDiagnostic ( CodeProfileOutsideRoot , "selected profile resolves outside the configuration root" , nil ) }
}
for _ , existing := range sources {
if existing . Path != "" && existing . Path == canonical {
return nil , cloneSourceInfos ( sources ) , [ ] Diagnostic { importDiagnostic ( CodeProfileRepeated , "selected profile is already present in the base source graph" , nil ) }
}
}
if len ( sources ) + 1 > limits . sources {
return nil , cloneSourceInfos ( sources ) , [ ] Diagnostic { importDiagnostic ( CodeProfileLimitExceeded , "selected profile exceeds the source-count limit" , nil ) }
}
root , err := os . OpenRoot ( rootPath )
if err != nil {
value := globFilesystemDiagnostic ( err , "configuration root could not be opened for the selected profile" , Location { } )
value . Primary = nil
return nil , cloneSourceInfos ( sources ) , [ ] Diagnostic { value }
}
relative , err := filepath . Rel ( rootPath , canonical )
if err != nil || filepath . IsAbs ( relative ) || relative == ".." || strings . HasPrefix ( relative , ".." + string ( filepath . Separator ) ) {
_ = root . Close ( )
return nil , cloneSourceInfos ( sources ) , [ ] Diagnostic { importDiagnostic ( CodeProfileOutsideRoot , "selected profile could not be addressed inside the configuration root" , nil ) }
}
sourceID := "source-" + strconv . Itoa ( len ( sources ) + 1 )
profileLocation := & Location { SourceID : sourceID }
profileSource := SourceInfo { ID : sourceID , Label : filepath . ToSlash ( filepath . Join ( "profiles" , name + ".yml" ) ) , Path : canonical , Layer : LayerProfile }
sources = append ( cloneSourceInfos ( sources ) , profileSource )
data , diagnostic := readProfileFile ( root , relative , sourceID , limits . bytes - base . inputBytes )
if diagnostic != nil {
_ = root . Close ( )
return nil , sources , [ ] Diagnostic { * diagnostic }
}
profileRoot , issues := yamladapter . Parse ( bytes . NewReader ( data ) , sourceID , yamladapter . Limits { Depth : maxDocumentDepth , Nodes : maxDocumentNodes } )
if len ( issues ) != 0 {
_ = root . Close ( )
2026-09-07 01:04:08 +00:00
return nil , sources , parserDiagnostics ( sourceID , issues )
2026-09-06 21:03:45 +00:00
}
nodeCount := countInputNodes ( profileRoot )
if base . inputNodes + nodeCount > limits . nodes {
_ = root . Close ( )
return nil , sources , [ ] Diagnostic { importDiagnostic ( CodeProfileLimitExceeded , "selected profile exceeds the aggregate node limit" , profileLocation ) }
}
if diagnostics := profileDirectiveDiagnostics ( profileRoot , Path { } ) ; len ( diagnostics ) != 0 {
_ = root . Close ( )
return nil , sources , diagnostics
}
if versionNode := fragmentVersionClaim ( profileRoot , rule ) ; versionNode != nil {
location := publicLocation ( versionNode . Location )
_ = root . Close ( )
return nil , sources , [ ] Diagnostic { {
Code : CodeProfileVersion , Severity : SeverityError , Path : rule . Path . clone ( ) ,
Message : "profiles cannot contribute to or replace the schema-version path" , Primary : & location ,
} }
}
if err := root . Close ( ) ; err != nil {
return nil , sources , [ ] Diagnostic { importDiagnostic ( CodeImportRead , "configuration root could not be closed after reading the selected profile" , nil ) }
}
profiled , diagnostics := overlayDocuments ( base , profileRoot )
if profiled != nil {
profiled . inputBytes = base . inputBytes + int64 ( len ( data ) )
profiled . inputNodes = base . inputNodes + nodeCount
}
return profiled , sources , diagnostics
}
func readProfileFile ( root * os . Root , relative , sourceID string , remaining int64 ) ( [ ] byte , * Diagnostic ) {
limit := min ( int64 ( maxSourceBytes ) , remaining )
if limit < 0 {
limit = 0
}
2026-09-07 01:06:35 +00:00
data , failure := readRegularFile ( relative , root . Stat , root . Open , limit )
if failure != nil {
value := sourceReadDiagnostic ( failure , sourceID , CodeProfileLimitExceeded , "selected profile exceeds a source or aggregate byte limit" )
2026-09-06 21:03:45 +00:00
return nil , & value
}
return data , nil
}
func profileDirectiveDiagnostics ( node * document . Node , current Path ) [ ] Diagnostic {
if node . Kind == document . Mapping {
for _ , entry := range node . Mapping {
childPath := current . Key ( entry . Key )
if entry . Key == "_configkit" {
location := publicLocation ( entry . KeyLocation )
return [ ] Diagnostic { {
Code : CodeProfileDirective , Severity : SeverityError , Path : childPath ,
Message : "profiles cannot contain configuration directives" , Primary : & location ,
} }
}
if diagnostics := profileDirectiveDiagnostics ( entry . Value , childPath ) ; len ( diagnostics ) != 0 {
return diagnostics
}
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
if diagnostics := profileDirectiveDiagnostics ( child , current . Index ( index ) ) ; len ( diagnostics ) != 0 {
return diagnostics
}
}
}
return nil
}