add support for env variables to the DSL
This commit is contained in:
98
lang/debug_env_test.go
Normal file
98
lang/debug_env_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleEnvVar(t *testing.T) {
|
||||
// Test the simplest possible environment variable syntax
|
||||
input := `
|
||||
server MyApp {
|
||||
host env "HOST"
|
||||
}
|
||||
`
|
||||
|
||||
ast, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error: %v", err)
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
if server.Settings[0].Host.EnvVar == nil {
|
||||
t.Fatalf("Expected environment variable")
|
||||
}
|
||||
if server.Settings[0].Host.EnvVar.Name != "HOST" {
|
||||
t.Errorf("Expected HOST, got %s", server.Settings[0].Host.EnvVar.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiteralValue(t *testing.T) {
|
||||
// Test that literal values still work
|
||||
input := `
|
||||
server MyApp {
|
||||
host "localhost"
|
||||
}
|
||||
`
|
||||
|
||||
ast, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error: %v", err)
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
if server.Settings[0].Host.Literal == nil {
|
||||
t.Fatalf("Expected literal value")
|
||||
}
|
||||
if *server.Settings[0].Host.Literal != "localhost" {
|
||||
t.Errorf("Expected localhost, got %s", *server.Settings[0].Host.Literal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvVarWithDefault(t *testing.T) {
|
||||
// Test environment variable with default value
|
||||
input := `
|
||||
server MyApp {
|
||||
host env "HOST" default "localhost"
|
||||
}
|
||||
`
|
||||
|
||||
ast, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error: %v", err)
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
envVar := server.Settings[0].Host.EnvVar
|
||||
if envVar == nil {
|
||||
t.Fatalf("Expected environment variable")
|
||||
}
|
||||
if envVar.Name != "HOST" {
|
||||
t.Errorf("Expected HOST, got %s", envVar.Name)
|
||||
}
|
||||
if envVar.Default == nil || *envVar.Default != "localhost" {
|
||||
t.Errorf("Expected default localhost")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvVarRequired(t *testing.T) {
|
||||
// Test environment variable with required flag
|
||||
input := `
|
||||
server MyApp {
|
||||
api_key env "API_KEY" required
|
||||
}
|
||||
`
|
||||
|
||||
ast, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error: %v", err)
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
envVar := server.Settings[0].APIKey.EnvVar
|
||||
if envVar == nil {
|
||||
t.Fatalf("Expected environment variable")
|
||||
}
|
||||
if !envVar.Required {
|
||||
t.Errorf("Expected required to be true")
|
||||
}
|
||||
}
|
86
lang/lang.go
86
lang/lang.go
@ -4,12 +4,12 @@ import (
|
||||
"github.com/alecthomas/participle/v2"
|
||||
)
|
||||
|
||||
// Root AST node containing all definitions
|
||||
// AST Root AST node containing all definitions
|
||||
type AST struct {
|
||||
Definitions []Definition `parser:"@@*"`
|
||||
}
|
||||
|
||||
// Union type for top-level definitions
|
||||
// Definition Union type for top-level definitions
|
||||
type Definition struct {
|
||||
Server *Server `parser:"@@"`
|
||||
Entity *Entity `parser:"| @@"`
|
||||
@ -17,25 +17,48 @@ type Definition struct {
|
||||
Page *Page `parser:"| @@"`
|
||||
}
|
||||
|
||||
// Clean server syntax
|
||||
// ConfigValue Flexible value that can be literal or environment variable
|
||||
type ConfigValue struct {
|
||||
Literal *string `parser:"@String"`
|
||||
EnvVar *EnvVar `parser:"| @@"`
|
||||
}
|
||||
|
||||
// EnvVar Environment variable configuration
|
||||
type EnvVar struct {
|
||||
Name string `parser:"'env' @String"`
|
||||
Default *string `parser:"('default' @String)?"`
|
||||
Required bool `parser:"@'required'?"`
|
||||
}
|
||||
|
||||
// Server Clean server syntax
|
||||
type Server struct {
|
||||
Name string `parser:"'server' @Ident"`
|
||||
Settings []ServerSetting `parser:"('{' @@* '}')?"` // Block-delimited settings for consistency
|
||||
}
|
||||
|
||||
type ServerSetting struct {
|
||||
Host *string `parser:"('host' @String)"`
|
||||
Port *int `parser:"| ('port' @Int)"`
|
||||
Host *ConfigValue `parser:"('host' @@)"`
|
||||
Port *IntValue `parser:"| ('port' @@)"`
|
||||
DatabaseURL *ConfigValue `parser:"| ('database_url' @@)"`
|
||||
APIKey *ConfigValue `parser:"| ('api_key' @@)"`
|
||||
SSLCert *ConfigValue `parser:"| ('ssl_cert' @@)"`
|
||||
SSLKey *ConfigValue `parser:"| ('ssl_key' @@)"`
|
||||
}
|
||||
|
||||
// Clean entity syntax with better readability
|
||||
// IntValue Similar to ConfigValue but for integers
|
||||
type IntValue struct {
|
||||
Literal *int `parser:"@Int"`
|
||||
EnvVar *EnvVar `parser:"| @@"`
|
||||
}
|
||||
|
||||
// Entity Clean entity syntax with better readability
|
||||
type Entity struct {
|
||||
Name string `parser:"'entity' @Ident"`
|
||||
Description *string `parser:"('desc' @String)?"`
|
||||
Fields []Field `parser:"('{' @@* '}')?"` // Block-delimited fields for consistency
|
||||
}
|
||||
|
||||
// Much cleaner field syntax
|
||||
// Field Detailed field syntax
|
||||
type Field struct {
|
||||
Name string `parser:"@Ident ':'"`
|
||||
Type string `parser:"@Ident"`
|
||||
@ -45,15 +68,24 @@ type Field struct {
|
||||
Default *string `parser:"('default' @String)?"`
|
||||
Validations []Validation `parser:"@@*"`
|
||||
Relationship *Relationship `parser:"@@?"`
|
||||
Endpoints []string `parser:"('endpoints' '[' @Ident (',' @Ident)* ']')?"` // with transforms this might not be needed
|
||||
Transform []Transform `parser:"@@*"`
|
||||
}
|
||||
|
||||
// Simple validation syntax
|
||||
// Transform Field transformation specification
|
||||
type Transform struct {
|
||||
Type string `parser:"'transform' @Ident"`
|
||||
Column *string `parser:"('to' @Ident)?"`
|
||||
Direction *string `parser:"('on' @('input' | 'output' | 'both'))?"`
|
||||
}
|
||||
|
||||
// Validation Simple validation syntax
|
||||
type Validation struct {
|
||||
Type string `parser:"'validate' @Ident"`
|
||||
Value *string `parser:"@String?"`
|
||||
}
|
||||
|
||||
// Clear relationship syntax
|
||||
// Relationship Clear relationship syntax
|
||||
type Relationship struct {
|
||||
Type string `parser:"'relates' 'to' @Ident"`
|
||||
Cardinality string `parser:"'as' @('one' | 'many')"`
|
||||
@ -73,7 +105,7 @@ type Endpoint struct {
|
||||
CustomLogic *string `parser:"('custom' @String)? '}')?"` // Close block after all content
|
||||
}
|
||||
|
||||
// Clean parameter syntax
|
||||
// EndpointParam Clean parameter syntax
|
||||
type EndpointParam struct {
|
||||
Name string `parser:"'param' @Ident ':'"`
|
||||
Type string `parser:"@Ident"`
|
||||
@ -81,14 +113,14 @@ type EndpointParam struct {
|
||||
Source string `parser:"'from' @('path' | 'query' | 'body')"`
|
||||
}
|
||||
|
||||
// Response specification
|
||||
// ResponseSpec Response specification
|
||||
type ResponseSpec struct {
|
||||
Type string `parser:"'returns' @Ident"`
|
||||
Format *string `parser:"('as' @String)?"`
|
||||
Fields []string `parser:"('fields' '[' @Ident (',' @Ident)* ']')?"`
|
||||
}
|
||||
|
||||
// Enhanced Page definitions with unified section model
|
||||
// Page Enhanced Page definitions with unified section model
|
||||
type Page struct {
|
||||
Name string `parser:"'page' @Ident"`
|
||||
Path string `parser:"'at' @String"`
|
||||
@ -101,13 +133,13 @@ type Page struct {
|
||||
Components []Component `parser:"@@* '}')?"` // Direct components within the block
|
||||
}
|
||||
|
||||
// Meta tags for SEO
|
||||
// MetaTag Meta tags for SEO
|
||||
type MetaTag struct {
|
||||
Name string `parser:"'meta' @Ident"`
|
||||
Content string `parser:"@String"`
|
||||
}
|
||||
|
||||
// Unified Section type that replaces Container, Tab, Panel, Modal, MasterDetail
|
||||
// Section Unified Section type that replaces Container, Tab, Panel, Modal, MasterDetail
|
||||
type Section struct {
|
||||
Name string `parser:"'section' @Ident"`
|
||||
Type *string `parser:"('type' @('container' | 'tab' | 'panel' | 'modal' | 'master' | 'detail'))?"`
|
||||
@ -120,7 +152,7 @@ type Section struct {
|
||||
Elements []SectionElement `parser:"('{' @@* '}')?"` // Block-delimited elements for unambiguous nesting
|
||||
}
|
||||
|
||||
// New unified element type for sections
|
||||
// SectionElement New unified element type for sections
|
||||
type SectionElement struct {
|
||||
Attribute *SectionAttribute `parser:"@@"`
|
||||
Component *Component `parser:"| @@"`
|
||||
@ -128,7 +160,7 @@ type SectionElement struct {
|
||||
When *WhenCondition `parser:"| @@"`
|
||||
}
|
||||
|
||||
// Flexible section attributes (replaces complex config types)
|
||||
// SectionAttribute Flexible section attributes (replaces complex config types)
|
||||
type SectionAttribute struct {
|
||||
DataSource *string `parser:"('data' 'from' @String)"`
|
||||
Style *string `parser:"| ('style' @String)"`
|
||||
@ -137,14 +169,14 @@ type SectionAttribute struct {
|
||||
Theme *string `parser:"| ('theme' @String)"`
|
||||
}
|
||||
|
||||
// Simplified Component with unified attributes - reordered for better parsing
|
||||
// Component Simplified Component with unified attributes - reordered for better parsing
|
||||
type Component struct {
|
||||
Type string `parser:"'component' @Ident"`
|
||||
Entity *string `parser:"('for' @Ident)?"`
|
||||
Elements []ComponentElement `parser:"('{' @@* '}')?"` // Parse everything inside the block
|
||||
}
|
||||
|
||||
// Enhanced ComponentElement with recursive section support - now includes attributes
|
||||
// ComponentElement Enhanced ComponentElement with recursive section support - now includes attributes
|
||||
type ComponentElement struct {
|
||||
Attribute *ComponentAttr `parser:"@@"` // Component attributes can be inside the block
|
||||
Field *ComponentField `parser:"| @@"`
|
||||
@ -153,7 +185,7 @@ type ComponentElement struct {
|
||||
When *WhenCondition `parser:"| @@"`
|
||||
}
|
||||
|
||||
// Simplified component attributes using key-value pattern - reordered for precedence
|
||||
// ComponentAttr Simplified component attributes using key-value pattern - reordered for precedence
|
||||
type ComponentAttr struct {
|
||||
DataSource *string `parser:"('data' 'from' @String)"`
|
||||
Fields []string `parser:"| ('fields' '[' @Ident (',' @Ident)* ']')"`
|
||||
@ -164,14 +196,14 @@ type ComponentAttr struct {
|
||||
Validate bool `parser:"| @'validate'"`
|
||||
}
|
||||
|
||||
// Enhanced component field with detailed configuration using flexible attributes
|
||||
// ComponentField Enhanced component field with detailed configuration using flexible attributes
|
||||
type ComponentField struct {
|
||||
Name string `parser:"'field' @Ident"`
|
||||
Type string `parser:"'type' @Ident"`
|
||||
Attributes []ComponentFieldAttribute `parser:"@@* ('{' @@* '}')?"` // Support both inline and block attributes
|
||||
}
|
||||
|
||||
// Flexible field attribute system
|
||||
// ComponentFieldAttribute Flexible field attribute system
|
||||
type ComponentFieldAttribute struct {
|
||||
Label *string `parser:"('label' @String)"`
|
||||
Placeholder *string `parser:"| ('placeholder' @String)"`
|
||||
@ -192,18 +224,18 @@ type ComponentFieldAttribute struct {
|
||||
Validation *ComponentValidation `parser:"| @@"`
|
||||
}
|
||||
|
||||
// Field relationship for autocomplete and select fields
|
||||
// FieldRelation Field relationship for autocomplete and select fields
|
||||
type FieldRelation struct {
|
||||
Type string `parser:"'relates' 'to' @Ident"`
|
||||
}
|
||||
|
||||
// Component validation
|
||||
// ComponentValidation Component validation
|
||||
type ComponentValidation struct {
|
||||
Type string `parser:"'validate' @Ident"`
|
||||
Value *string `parser:"@String?"`
|
||||
}
|
||||
|
||||
// Enhanced WhenCondition with recursive support for both sections and components
|
||||
// WhenCondition Enhanced WhenCondition with recursive support for both sections and components
|
||||
type WhenCondition struct {
|
||||
Field string `parser:"'when' @Ident"`
|
||||
Operator string `parser:"@('equals' | 'not_equals' | 'contains')"`
|
||||
@ -214,14 +246,14 @@ type WhenCondition struct {
|
||||
Buttons []ComponentButton `parser:"@@* '}')?"` // Block-delimited for unambiguous nesting
|
||||
}
|
||||
|
||||
// Simplified button with flexible attribute ordering
|
||||
// ComponentButton Simplified button with flexible attribute ordering
|
||||
type ComponentButton struct {
|
||||
Name string `parser:"'button' @Ident"`
|
||||
Label string `parser:"'label' @String"`
|
||||
Attributes []ComponentButtonAttr `parser:"@@*"`
|
||||
}
|
||||
|
||||
// Flexible button attribute system - each attribute is a separate alternative
|
||||
// ComponentButtonAttr Flexible button attribute system - each attribute is a separate alternative
|
||||
type ComponentButtonAttr struct {
|
||||
Style *ComponentButtonStyle `parser:"@@"`
|
||||
Icon *ComponentButtonIcon `parser:"| @@"`
|
||||
@ -233,7 +265,7 @@ type ComponentButtonAttr struct {
|
||||
Via *ComponentButtonVia `parser:"| @@"`
|
||||
}
|
||||
|
||||
// Individual button attribute types
|
||||
// ComponentButtonStyle Individual button attribute types
|
||||
type ComponentButtonStyle struct {
|
||||
Value string `parser:"'style' @String"`
|
||||
}
|
||||
|
342
lang/parser_env_test.go
Normal file
342
lang/parser_env_test.go
Normal file
@ -0,0 +1,342 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServerWithEnvironmentVariables(t *testing.T) {
|
||||
input := `
|
||||
server MyApp {
|
||||
host env "HOST" default "localhost"
|
||||
port env "PORT" default "8080"
|
||||
database_url env "DATABASE_URL" required
|
||||
api_key env "API_SECRET_KEY" required
|
||||
}
|
||||
`
|
||||
|
||||
ast, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error: %v", err)
|
||||
}
|
||||
|
||||
if len(ast.Definitions) != 1 {
|
||||
t.Fatalf("Expected 1 definition, got %d", len(ast.Definitions))
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
if server == nil {
|
||||
t.Fatalf("Expected server definition")
|
||||
}
|
||||
|
||||
if server.Name != "MyApp" {
|
||||
t.Errorf("Expected server name 'MyApp', got '%s'", server.Name)
|
||||
}
|
||||
|
||||
if len(server.Settings) != 4 {
|
||||
t.Fatalf("Expected 4 settings, got %d", len(server.Settings))
|
||||
}
|
||||
|
||||
// Test host setting
|
||||
hostSetting := server.Settings[0]
|
||||
if hostSetting.Host == nil {
|
||||
t.Fatalf("Expected host setting")
|
||||
}
|
||||
if hostSetting.Host.EnvVar == nil {
|
||||
t.Fatalf("Expected host to be environment variable")
|
||||
}
|
||||
if hostSetting.Host.EnvVar.Name != "HOST" {
|
||||
t.Errorf("Expected env var name 'HOST', got '%s'", hostSetting.Host.EnvVar.Name)
|
||||
}
|
||||
if hostSetting.Host.EnvVar.Default == nil || *hostSetting.Host.EnvVar.Default != "localhost" {
|
||||
t.Errorf("Expected default 'localhost'")
|
||||
}
|
||||
|
||||
// Test port setting
|
||||
portSetting := server.Settings[1]
|
||||
if portSetting.Port == nil {
|
||||
t.Fatalf("Expected port setting")
|
||||
}
|
||||
if portSetting.Port.EnvVar == nil {
|
||||
t.Fatalf("Expected port to be environment variable")
|
||||
}
|
||||
if portSetting.Port.EnvVar.Name != "PORT" {
|
||||
t.Errorf("Expected env var name 'PORT', got '%s'", portSetting.Port.EnvVar.Name)
|
||||
}
|
||||
if portSetting.Port.EnvVar.Default == nil || *portSetting.Port.EnvVar.Default != "8080" {
|
||||
t.Errorf("Expected default '8080'")
|
||||
}
|
||||
|
||||
// Test required database_url
|
||||
dbSetting := server.Settings[2]
|
||||
if dbSetting.DatabaseURL == nil {
|
||||
t.Fatalf("Expected database_url setting")
|
||||
}
|
||||
if dbSetting.DatabaseURL.EnvVar == nil {
|
||||
t.Fatalf("Expected database_url to be environment variable")
|
||||
}
|
||||
if !dbSetting.DatabaseURL.EnvVar.Required {
|
||||
t.Errorf("Expected database_url to be required")
|
||||
}
|
||||
|
||||
// Test required api_key
|
||||
apiSetting := server.Settings[3]
|
||||
if apiSetting.APIKey == nil {
|
||||
t.Fatalf("Expected api_key setting")
|
||||
}
|
||||
if apiSetting.APIKey.EnvVar == nil {
|
||||
t.Fatalf("Expected api_key to be environment variable")
|
||||
}
|
||||
if !apiSetting.APIKey.EnvVar.Required {
|
||||
t.Errorf("Expected api_key to be required")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerWithMixedValues(t *testing.T) {
|
||||
input := `
|
||||
server MyApp {
|
||||
host "localhost"
|
||||
port env "PORT" default "8080"
|
||||
database_url env "DATABASE_URL" required
|
||||
}
|
||||
`
|
||||
|
||||
ast, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error: %v", err)
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
if server == nil {
|
||||
t.Fatalf("Expected server definition")
|
||||
}
|
||||
|
||||
// Test literal host
|
||||
hostSetting := server.Settings[0]
|
||||
if hostSetting.Host == nil {
|
||||
t.Fatalf("Expected host setting")
|
||||
}
|
||||
if hostSetting.Host.Literal == nil {
|
||||
t.Fatalf("Expected host to be literal value")
|
||||
}
|
||||
if *hostSetting.Host.Literal != "localhost" {
|
||||
t.Errorf("Expected literal value 'localhost', got '%s'", *hostSetting.Host.Literal)
|
||||
}
|
||||
|
||||
// Test env port
|
||||
portSetting := server.Settings[1]
|
||||
if portSetting.Port == nil {
|
||||
t.Fatalf("Expected port setting")
|
||||
}
|
||||
if portSetting.Port.EnvVar == nil {
|
||||
t.Fatalf("Expected port to be environment variable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerWithLiteralPort(t *testing.T) {
|
||||
input := `
|
||||
server MyApp {
|
||||
host "localhost"
|
||||
port 8080
|
||||
}
|
||||
`
|
||||
|
||||
ast, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error: %v", err)
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
portSetting := server.Settings[1]
|
||||
if portSetting.Port == nil {
|
||||
t.Fatalf("Expected port setting")
|
||||
}
|
||||
if portSetting.Port.Literal == nil {
|
||||
t.Fatalf("Expected port to be literal value")
|
||||
}
|
||||
if *portSetting.Port.Literal != 8080 {
|
||||
t.Errorf("Expected literal port 8080, got %d", *portSetting.Port.Literal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfigurationsInAnyOrder(t *testing.T) {
|
||||
// Test that server settings can be defined in any order
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
name: "host first, then port",
|
||||
input: `
|
||||
server MyApp {
|
||||
host "localhost"
|
||||
port 8080
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "port first, then host",
|
||||
input: `
|
||||
server MyApp {
|
||||
port 8080
|
||||
host "localhost"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "mixed literal and env vars in random order",
|
||||
input: `
|
||||
server MyApp {
|
||||
api_key env "API_KEY" required
|
||||
host "localhost"
|
||||
database_url env "DATABASE_URL" default "postgres://localhost:5432/myapp"
|
||||
port 8080
|
||||
ssl_cert env "SSL_CERT" required
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "all env vars in different order",
|
||||
input: `
|
||||
server MyApp {
|
||||
ssl_key env "SSL_KEY" required
|
||||
port env "PORT" default "8080"
|
||||
database_url env "DATABASE_URL" required
|
||||
host env "HOST" default "localhost"
|
||||
api_key env "API_KEY" required
|
||||
ssl_cert env "SSL_CERT" required
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "all literal values in different order",
|
||||
input: `
|
||||
server MyApp {
|
||||
database_url "postgres://localhost:5432/myapp"
|
||||
port 3000
|
||||
ssl_key "/path/to/ssl.key"
|
||||
host "0.0.0.0"
|
||||
api_key "secret123"
|
||||
ssl_cert "/path/to/ssl.crt"
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ast, err := ParseInput(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error for %s: %v", tt.name, err)
|
||||
}
|
||||
|
||||
if len(ast.Definitions) != 1 {
|
||||
t.Fatalf("Expected 1 definition, got %d", len(ast.Definitions))
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
if server == nil {
|
||||
t.Fatalf("Expected server definition")
|
||||
}
|
||||
|
||||
if server.Name != "MyApp" {
|
||||
t.Errorf("Expected server name 'MyApp', got '%s'", server.Name)
|
||||
}
|
||||
|
||||
// Verify that we can parse any number of settings in any order
|
||||
if len(server.Settings) < 1 {
|
||||
t.Fatalf("Expected at least 1 setting, got %d", len(server.Settings))
|
||||
}
|
||||
|
||||
// Test that we can access specific settings regardless of order
|
||||
var hasHost, hasPort bool
|
||||
for _, setting := range server.Settings {
|
||||
if setting.Host != nil {
|
||||
hasHost = true
|
||||
}
|
||||
if setting.Port != nil {
|
||||
hasPort = true
|
||||
}
|
||||
}
|
||||
|
||||
// For the first two tests, verify both host and port are present
|
||||
if tt.name == "host first, then port" || tt.name == "port first, then host" {
|
||||
if !hasHost {
|
||||
t.Errorf("Expected to find host setting")
|
||||
}
|
||||
if !hasPort {
|
||||
t.Errorf("Expected to find port setting")
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Successfully parsed %d settings for test '%s'", len(server.Settings), tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfigurationValidation(t *testing.T) {
|
||||
// Test that we can properly validate different configurations
|
||||
input := `
|
||||
server ProductionApp {
|
||||
ssl_cert env "SSL_CERT_PATH" required
|
||||
database_url env "DATABASE_URL" required
|
||||
host env "HOST" default "0.0.0.0"
|
||||
api_key env "SECRET_API_KEY" required
|
||||
port env "PORT" default "443"
|
||||
ssl_key env "SSL_KEY_PATH" required
|
||||
}
|
||||
`
|
||||
|
||||
ast, err := ParseInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse error: %v", err)
|
||||
}
|
||||
|
||||
server := ast.Definitions[0].Server
|
||||
if server.Name != "ProductionApp" {
|
||||
t.Errorf("Expected server name 'ProductionApp', got '%s'", server.Name)
|
||||
}
|
||||
|
||||
// Create a map to easily check for specific settings
|
||||
settingsMap := make(map[string]interface{})
|
||||
for _, setting := range server.Settings {
|
||||
if setting.Host != nil {
|
||||
settingsMap["host"] = setting.Host
|
||||
}
|
||||
if setting.Port != nil {
|
||||
settingsMap["port"] = setting.Port
|
||||
}
|
||||
if setting.DatabaseURL != nil {
|
||||
settingsMap["database_url"] = setting.DatabaseURL
|
||||
}
|
||||
if setting.APIKey != nil {
|
||||
settingsMap["api_key"] = setting.APIKey
|
||||
}
|
||||
if setting.SSLCert != nil {
|
||||
settingsMap["ssl_cert"] = setting.SSLCert
|
||||
}
|
||||
if setting.SSLKey != nil {
|
||||
settingsMap["ssl_key"] = setting.SSLKey
|
||||
}
|
||||
}
|
||||
|
||||
// Verify all expected settings are present
|
||||
expectedSettings := []string{"host", "port", "database_url", "api_key", "ssl_cert", "ssl_key"}
|
||||
for _, expected := range expectedSettings {
|
||||
if _, exists := settingsMap[expected]; !exists {
|
||||
t.Errorf("Expected to find setting '%s'", expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify host has default value
|
||||
if hostSetting, ok := settingsMap["host"].(*ConfigValue); ok {
|
||||
if hostSetting.EnvVar == nil {
|
||||
t.Errorf("Expected host to be environment variable")
|
||||
} else if hostSetting.EnvVar.Default == nil || *hostSetting.EnvVar.Default != "0.0.0.0" {
|
||||
t.Errorf("Expected host default to be '0.0.0.0'")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify ssl_cert is required
|
||||
if sslCertSetting, ok := settingsMap["ssl_cert"].(*ConfigValue); ok {
|
||||
if sslCertSetting.EnvVar == nil {
|
||||
t.Errorf("Expected ssl_cert to be environment variable")
|
||||
} else if !sslCertSetting.EnvVar.Required {
|
||||
t.Errorf("Expected ssl_cert to be required")
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,35 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Helper functions for creating ConfigValue and IntValue instances
|
||||
func literalConfigValue(value string) *ConfigValue {
|
||||
return &ConfigValue{Literal: &value}
|
||||
}
|
||||
|
||||
func literalIntValue(value int) *IntValue {
|
||||
return &IntValue{Literal: &value}
|
||||
}
|
||||
|
||||
func envConfigValue(name string, defaultValue *string, required bool) *ConfigValue {
|
||||
return &ConfigValue{
|
||||
EnvVar: &EnvVar{
|
||||
Name: name,
|
||||
Default: defaultValue,
|
||||
Required: required,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func envIntValue(name string, defaultValue *string, required bool) *IntValue {
|
||||
return &IntValue{
|
||||
EnvVar: &EnvVar{
|
||||
Name: name,
|
||||
Default: defaultValue,
|
||||
Required: required,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseServerDefinitions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -23,8 +52,8 @@ func TestParseServerDefinitions(t *testing.T) {
|
||||
Server: &Server{
|
||||
Name: "MyApp",
|
||||
Settings: []ServerSetting{
|
||||
{Host: stringPtr("localhost")},
|
||||
{Port: intPtr(8080)},
|
||||
{Host: literalConfigValue("localhost")},
|
||||
{Port: literalIntValue(8080)},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -43,7 +72,7 @@ func TestParseServerDefinitions(t *testing.T) {
|
||||
Server: &Server{
|
||||
Name: "WebApp",
|
||||
Settings: []ServerSetting{
|
||||
{Host: stringPtr("0.0.0.0")},
|
||||
{Host: literalConfigValue("0.0.0.0")},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -62,7 +91,7 @@ func TestParseServerDefinitions(t *testing.T) {
|
||||
Server: &Server{
|
||||
Name: "APIServer",
|
||||
Settings: []ServerSetting{
|
||||
{Port: intPtr(3000)},
|
||||
{Port: literalIntValue(3000)},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -21,7 +21,68 @@ func serverEqual(got, want Server) bool {
|
||||
}
|
||||
|
||||
func serverSettingEqual(got, want ServerSetting) bool {
|
||||
return stringPtrEqual(got.Host, want.Host) && intPtrEqual(got.Port, want.Port)
|
||||
return configValueEqual(got.Host, want.Host) &&
|
||||
intValueEqual(got.Port, want.Port) &&
|
||||
configValueEqual(got.DatabaseURL, want.DatabaseURL) &&
|
||||
configValueEqual(got.APIKey, want.APIKey) &&
|
||||
configValueEqual(got.SSLCert, want.SSLCert) &&
|
||||
configValueEqual(got.SSLKey, want.SSLKey)
|
||||
}
|
||||
|
||||
func configValueEqual(got, want *ConfigValue) bool {
|
||||
if got == nil && want == nil {
|
||||
return true
|
||||
}
|
||||
if got == nil || want == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check literal values
|
||||
if got.Literal != nil && want.Literal != nil {
|
||||
return *got.Literal == *want.Literal
|
||||
}
|
||||
if got.Literal != nil || want.Literal != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check environment variables
|
||||
if got.EnvVar != nil && want.EnvVar != nil {
|
||||
return envVarEqual(*got.EnvVar, *want.EnvVar)
|
||||
}
|
||||
return got.EnvVar == nil && want.EnvVar == nil
|
||||
}
|
||||
|
||||
func intValueEqual(got, want *IntValue) bool {
|
||||
if got == nil && want == nil {
|
||||
return true
|
||||
}
|
||||
if got == nil || want == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check literal values
|
||||
if got.Literal != nil && want.Literal != nil {
|
||||
return *got.Literal == *want.Literal
|
||||
}
|
||||
if got.Literal != nil || want.Literal != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check environment variables
|
||||
if got.EnvVar != nil && want.EnvVar != nil {
|
||||
return envVarEqual(*got.EnvVar, *want.EnvVar)
|
||||
}
|
||||
return got.EnvVar == nil && want.EnvVar == nil
|
||||
}
|
||||
|
||||
func envVarEqual(got, want EnvVar) bool {
|
||||
if got.Name != want.Name {
|
||||
return false
|
||||
}
|
||||
if got.Required != want.Required {
|
||||
return false
|
||||
}
|
||||
return stringPtrEqual(got.Default, want.Default)
|
||||
}
|
||||
|
||||
func entityEqual(got, want Entity) bool {
|
||||
|
Reference in New Issue
Block a user