add support for env variables to the DSL
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
/masonry.exe
|
||||
/.idea/copilotDiffState.xml
|
||||
|
@ -68,15 +68,9 @@ func main() {
|
||||
// -- end of local database code --
|
||||
|
||||
// Uncomment these lines if you need automatic migration
|
||||
// err = gormDB.AutoMigrate(&pb.UserORM{})
|
||||
// err = gormDB.AutoMigrate(&pb.UserORM{}, &pb.ProductORM{})
|
||||
// if err != nil {
|
||||
// logger.Printf("failed to migrate user: %s", err)
|
||||
// log.Fatalln(err)
|
||||
// return
|
||||
// }
|
||||
// err = gormDB.AutoMigrate(&pb.ProductORM{})
|
||||
// if err != nil {
|
||||
// logger.Printf("failed to migrate product: %s", err)
|
||||
// logger.Printf("failed to auto migrate database: %s", err)
|
||||
// log.Fatalln(err)
|
||||
// return
|
||||
// }
|
||||
|
@ -140,23 +140,9 @@ func (hi *HTMLInterpreter) generatePageHTML(page *lang.Page) (string, error) {
|
||||
// JavaScript for interactivity
|
||||
html.WriteString(" <script>\n")
|
||||
|
||||
// API Base URL configuration
|
||||
apiBaseURL := "http://localhost:8080"
|
||||
if hi.server != nil {
|
||||
host := "localhost"
|
||||
port := 8080
|
||||
for _, setting := range hi.server.Settings {
|
||||
if setting.Host != nil {
|
||||
host = *setting.Host
|
||||
}
|
||||
if setting.Port != nil {
|
||||
port = *setting.Port
|
||||
}
|
||||
}
|
||||
apiBaseURL = fmt.Sprintf("http://%s:%d", host, port)
|
||||
}
|
||||
// Generate server configuration code that handles env vars and defaults at runtime
|
||||
html.WriteString(hi.generateServerConfigJS())
|
||||
|
||||
html.WriteString(fmt.Sprintf(" const API_BASE_URL = '%s';\n", apiBaseURL))
|
||||
html.WriteString(" \n")
|
||||
html.WriteString(" // API helper functions\n")
|
||||
html.WriteString(" async function apiRequest(method, endpoint, data = null) {\n")
|
||||
@ -348,6 +334,86 @@ func (hi *HTMLInterpreter) generatePageHTML(page *lang.Page) (string, error) {
|
||||
return html.String(), nil
|
||||
}
|
||||
|
||||
// generateServerConfigJS generates JavaScript code that handles server configuration at runtime
|
||||
func (hi *HTMLInterpreter) generateServerConfigJS() string {
|
||||
var js strings.Builder
|
||||
|
||||
// Default API base URL
|
||||
js.WriteString(" // Server configuration\n")
|
||||
js.WriteString(" let apiHost = 'localhost';\n")
|
||||
js.WriteString(" let apiPort = 8080;\n\n")
|
||||
|
||||
if hi.server != nil {
|
||||
for _, setting := range hi.server.Settings {
|
||||
if setting.Host != nil {
|
||||
js.WriteString(hi.generateConfigValueJS("apiHost", setting.Host))
|
||||
}
|
||||
if setting.Port != nil {
|
||||
js.WriteString(hi.generateIntValueJS("apiPort", setting.Port))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
js.WriteString(" const API_BASE_URL = `http://${apiHost}:${apiPort}`;\n")
|
||||
js.WriteString(" console.log('API Base URL:', API_BASE_URL);\n")
|
||||
|
||||
return js.String()
|
||||
}
|
||||
|
||||
// generateConfigValueJS generates JavaScript code to resolve a ConfigValue at runtime
|
||||
func (hi *HTMLInterpreter) generateConfigValueJS(varName string, configValue *lang.ConfigValue) string {
|
||||
var js strings.Builder
|
||||
|
||||
if configValue.Literal != nil {
|
||||
// Simple literal assignment
|
||||
js.WriteString(fmt.Sprintf(" %s = %q;\n", varName, *configValue.Literal))
|
||||
} else if configValue.EnvVar != nil {
|
||||
// Environment variable resolution in browser (note: this is limited in browsers)
|
||||
// For client-side, we'll need to use a different approach since browsers can't access server env vars
|
||||
// We'll generate code that looks for the env var in localStorage or a global config object
|
||||
js.WriteString(fmt.Sprintf(" // Check for %s in global config or localStorage\n", configValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" if (window.CONFIG && window.CONFIG['%s']) {\n", configValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" %s = window.CONFIG['%s'];\n", varName, configValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" } else if (localStorage.getItem('%s')) {\n", configValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" %s = localStorage.getItem('%s');\n", varName, configValue.EnvVar.Name))
|
||||
|
||||
if configValue.EnvVar.Default != nil {
|
||||
js.WriteString(" } else {\n")
|
||||
js.WriteString(fmt.Sprintf(" %s = %q;\n", varName, *configValue.EnvVar.Default))
|
||||
}
|
||||
js.WriteString(" }\n")
|
||||
}
|
||||
|
||||
return js.String()
|
||||
}
|
||||
|
||||
// generateIntValueJS generates JavaScript code to resolve an IntValue at runtime
|
||||
func (hi *HTMLInterpreter) generateIntValueJS(varName string, intValue *lang.IntValue) string {
|
||||
var js strings.Builder
|
||||
|
||||
if intValue.Literal != nil {
|
||||
// Simple literal assignment
|
||||
js.WriteString(fmt.Sprintf(" %s = %d;\n", varName, *intValue.Literal))
|
||||
} else if intValue.EnvVar != nil {
|
||||
// Environment variable resolution for integers
|
||||
js.WriteString(fmt.Sprintf(" // Check for %s in global config or localStorage\n", intValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" if (window.CONFIG && window.CONFIG['%s']) {\n", intValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" const val = parseInt(window.CONFIG['%s']);\n", intValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" if (!isNaN(val)) %s = val;\n", varName))
|
||||
js.WriteString(fmt.Sprintf(" } else if (localStorage.getItem('%s')) {\n", intValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" const val = parseInt(localStorage.getItem('%s'));\n", intValue.EnvVar.Name))
|
||||
js.WriteString(fmt.Sprintf(" if (!isNaN(val)) %s = val;\n", varName))
|
||||
|
||||
if intValue.EnvVar.Default != nil {
|
||||
js.WriteString(" } else {\n")
|
||||
js.WriteString(fmt.Sprintf(" %s = %s;\n", varName, *intValue.EnvVar.Default))
|
||||
}
|
||||
js.WriteString(" }\n")
|
||||
}
|
||||
|
||||
return js.String()
|
||||
}
|
||||
|
||||
// generateSectionHTML creates HTML for a section
|
||||
func (hi *HTMLInterpreter) generateSectionHTML(section *lang.Section, indent int) (string, error) {
|
||||
var html strings.Builder
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"go/format"
|
||||
"masonry/lang"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
@ -69,6 +70,8 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"github.com/google/uuid"
|
||||
@ -362,25 +365,95 @@ func (si *ServerInterpreter) generateMainFunction() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Server configuration
|
||||
host := "localhost"
|
||||
port := 8080
|
||||
// Generate server configuration code that handles env vars and defaults at runtime
|
||||
code.WriteString("\n\t// Server configuration\n")
|
||||
code.WriteString(si.generateServerConfigCode())
|
||||
|
||||
if si.server != nil {
|
||||
for _, setting := range si.server.Settings {
|
||||
if setting.Host != nil {
|
||||
host = *setting.Host
|
||||
}
|
||||
if setting.Port != nil {
|
||||
port = *setting.Port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code.WriteString(fmt.Sprintf("\n\taddr := \"%s:%d\"\n", host, port))
|
||||
code.WriteString("\n\taddr := fmt.Sprintf(\"%s:%d\", host, port)\n")
|
||||
code.WriteString("\tfmt.Printf(\"Server starting on %s\\n\", addr)\n")
|
||||
code.WriteString("\tlog.Fatal(http.ListenAndServe(addr, r))\n")
|
||||
code.WriteString("}\n")
|
||||
|
||||
return code.String()
|
||||
}
|
||||
|
||||
// generateServerConfigCode generates Go code that handles server configuration at runtime
|
||||
func (si *ServerInterpreter) generateServerConfigCode() string {
|
||||
var code strings.Builder
|
||||
|
||||
// Default values
|
||||
hostDefault := "localhost"
|
||||
portDefault := 8080
|
||||
var hostConfigValueCode, portConfigValueCode string
|
||||
|
||||
if si.server != nil {
|
||||
for _, setting := range si.server.Settings {
|
||||
if setting.Host != nil {
|
||||
if setting.Host.EnvVar != nil && setting.Host.EnvVar.Default != nil {
|
||||
hostDefault = *setting.Host.EnvVar.Default
|
||||
}
|
||||
hostConfigValueCode = si.generateConfigValueCode("host", setting.Host)
|
||||
}
|
||||
if setting.Port != nil {
|
||||
if setting.Port.EnvVar != nil && setting.Port.EnvVar.Default != nil {
|
||||
if defInt, err := strconv.Atoi(*setting.Port.EnvVar.Default); err == nil {
|
||||
portDefault = defInt
|
||||
}
|
||||
}
|
||||
portConfigValueCode = si.generateIntValueCode("port", setting.Port)
|
||||
}
|
||||
}
|
||||
}
|
||||
code.WriteString(fmt.Sprintf("\thost := \"%s\"\n", hostDefault))
|
||||
code.WriteString(fmt.Sprintf("\tport := %d\n\n", portDefault))
|
||||
code.WriteString(hostConfigValueCode)
|
||||
code.WriteString(portConfigValueCode)
|
||||
|
||||
return code.String()
|
||||
}
|
||||
|
||||
// generateConfigValueCode generates Go code to resolve a ConfigValue at runtime
|
||||
func (si *ServerInterpreter) generateConfigValueCode(varName string, configValue *lang.ConfigValue) string {
|
||||
var code strings.Builder
|
||||
|
||||
if configValue.Literal != nil {
|
||||
// Simple literal assignment
|
||||
code.WriteString(fmt.Sprintf("\t%s = %q\n", varName, *configValue.Literal))
|
||||
} else if configValue.EnvVar != nil {
|
||||
// Environment variable with optional default
|
||||
code.WriteString(fmt.Sprintf("\tif envVal := os.Getenv(%q); envVal != \"\" {\n", configValue.EnvVar.Name))
|
||||
code.WriteString(fmt.Sprintf("\t\t%s = envVal\n", varName))
|
||||
|
||||
if configValue.EnvVar.Default != nil {
|
||||
code.WriteString("\t} else {\n")
|
||||
code.WriteString(fmt.Sprintf("\t\t%s = %q\n", varName, *configValue.EnvVar.Default))
|
||||
}
|
||||
code.WriteString("\t}\n")
|
||||
}
|
||||
|
||||
return code.String()
|
||||
}
|
||||
|
||||
// generateIntValueCode generates Go code to resolve an IntValue at runtime
|
||||
func (si *ServerInterpreter) generateIntValueCode(varName string, intValue *lang.IntValue) string {
|
||||
var code strings.Builder
|
||||
|
||||
if intValue.Literal != nil {
|
||||
// Simple literal assignment
|
||||
code.WriteString(fmt.Sprintf("\t%s = %d\n", varName, *intValue.Literal))
|
||||
} else if intValue.EnvVar != nil {
|
||||
// Environment variable with optional default
|
||||
code.WriteString(fmt.Sprintf("\tif envVal := os.Getenv(%q); envVal != \"\" {\n", intValue.EnvVar.Name))
|
||||
code.WriteString(fmt.Sprintf("\t\tif val, err := strconv.Atoi(envVal); err == nil {\n"))
|
||||
code.WriteString(fmt.Sprintf("\t\t\t%s = val\n", varName))
|
||||
code.WriteString("\t\t}\n")
|
||||
|
||||
if intValue.EnvVar.Default != nil {
|
||||
code.WriteString("\t} else {\n")
|
||||
code.WriteString(fmt.Sprintf("\t\t%s = %s\n", varName, *intValue.EnvVar.Default))
|
||||
}
|
||||
code.WriteString("\t}\n")
|
||||
}
|
||||
|
||||
return code.String()
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@ -115,28 +116,58 @@ func (ti *TemplateInterpreter) Interpret(masonryInput string, tmplText string) (
|
||||
"getHost": func(settings []lang.ServerSetting) string {
|
||||
for _, s := range settings {
|
||||
if s.Host != nil {
|
||||
return *s.Host
|
||||
if s.Host.Literal != nil {
|
||||
return "\"" + *s.Host.Literal + "\""
|
||||
}
|
||||
return fmt.Sprintf(`func() string { if v := os.Getenv("%s"); v != "" { return v }; return "%s" }()`, s.Host.EnvVar.Name, func() string {
|
||||
if s.Host.EnvVar.Default != nil {
|
||||
return *s.Host.EnvVar.Default
|
||||
}
|
||||
return "localhost"
|
||||
}())
|
||||
}
|
||||
}
|
||||
return "localhost"
|
||||
},
|
||||
"getPort": func(settings []lang.ServerSetting) int {
|
||||
"getPort": func(settings []lang.ServerSetting) string {
|
||||
for _, s := range settings {
|
||||
if s.Port != nil {
|
||||
return *s.Port
|
||||
if s.Port.Literal != nil {
|
||||
return strconv.Itoa(*s.Port.Literal)
|
||||
}
|
||||
return fmt.Sprintf(`func() string { if v := os.Getenv("%s"); v != "" { return v }; return "%s" }()`, s.Port.EnvVar.Name, func() string {
|
||||
if s.Port.EnvVar.Default != nil {
|
||||
return *s.Port.EnvVar.Default
|
||||
}
|
||||
return "8080"
|
||||
}())
|
||||
}
|
||||
}
|
||||
return 8080
|
||||
return "8080"
|
||||
},
|
||||
"getServerHostPort": func(settings []lang.ServerSetting) string {
|
||||
host := "localhost"
|
||||
port := 8080
|
||||
for _, s := range settings {
|
||||
if s.Host != nil {
|
||||
host = *s.Host
|
||||
if s.Host.Literal != nil {
|
||||
host = *s.Host.Literal
|
||||
}
|
||||
if s.Host.EnvVar != nil && s.Host.EnvVar.Default != nil {
|
||||
host = *s.Host.EnvVar.Default
|
||||
}
|
||||
// If it's an env var, keep the default
|
||||
}
|
||||
if s.Port != nil {
|
||||
port = *s.Port
|
||||
if s.Port.Literal != nil {
|
||||
port = *s.Port.Literal
|
||||
}
|
||||
if s.Port.EnvVar != nil && s.Port.EnvVar.Default != nil {
|
||||
if p, err := strconv.Atoi(*s.Port.EnvVar.Default); err == nil {
|
||||
port = p
|
||||
}
|
||||
}
|
||||
// If it's an env var, keep the default
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
@ -234,28 +265,58 @@ func NewTemplateRegistry() *TemplateRegistry {
|
||||
"getHost": func(settings []lang.ServerSetting) string {
|
||||
for _, s := range settings {
|
||||
if s.Host != nil {
|
||||
return *s.Host
|
||||
if s.Host.Literal != nil {
|
||||
return "\"" + *s.Host.Literal + "\""
|
||||
}
|
||||
return fmt.Sprintf(`func() string { if v := os.Getenv("%s"); v != "" { return v }; return "%s" }()`, s.Host.EnvVar.Name, func() string {
|
||||
if s.Host.EnvVar.Default != nil {
|
||||
return *s.Host.EnvVar.Default
|
||||
}
|
||||
return "localhost"
|
||||
}())
|
||||
}
|
||||
}
|
||||
return "localhost"
|
||||
},
|
||||
"getPort": func(settings []lang.ServerSetting) int {
|
||||
"getPort": func(settings []lang.ServerSetting) string {
|
||||
for _, s := range settings {
|
||||
if s.Port != nil {
|
||||
return *s.Port
|
||||
if s.Port.Literal != nil {
|
||||
return strconv.Itoa(*s.Port.Literal)
|
||||
}
|
||||
return fmt.Sprintf(`func() string { if v := os.Getenv("%s"); v != "" { return v }; return "%s" }()`, s.Port.EnvVar.Name, func() string {
|
||||
if s.Port.EnvVar.Default != nil {
|
||||
return *s.Port.EnvVar.Default
|
||||
}
|
||||
return "8080"
|
||||
}())
|
||||
}
|
||||
}
|
||||
return 8080
|
||||
return "8080"
|
||||
},
|
||||
"getServerHostPort": func(settings []lang.ServerSetting) string {
|
||||
host := "localhost"
|
||||
port := 8080
|
||||
for _, s := range settings {
|
||||
if s.Host != nil {
|
||||
host = *s.Host
|
||||
if s.Host.Literal != nil {
|
||||
host = *s.Host.Literal
|
||||
}
|
||||
if s.Host.EnvVar != nil && s.Host.EnvVar.Default != nil {
|
||||
host = *s.Host.EnvVar.Default
|
||||
}
|
||||
// If it's an env var, keep the default
|
||||
}
|
||||
if s.Port != nil {
|
||||
port = *s.Port
|
||||
if s.Port.Literal != nil {
|
||||
port = *s.Port.Literal
|
||||
}
|
||||
if s.Port.EnvVar != nil && s.Port.EnvVar.Default != nil {
|
||||
if p, err := strconv.Atoi(*s.Port.EnvVar.Default); err == nil {
|
||||
port = p
|
||||
}
|
||||
}
|
||||
// If it's an env var, keep the default
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
|
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 {
|
||||
|
@ -22,7 +22,8 @@ Masonry is a library that provides and implements all the basics necessary to bu
|
||||
|
||||
- [ ] Indicate values that are based on Environment variables
|
||||
- [ ] Yaml file to include custom template functions (Otto for JS)
|
||||
- [ ] On Entities, we should indicate what CRUD functions should be implemented instead of implementing all, or forcing the user to define each in the Endpoints section
|
||||
- [x] On Entities, we should indicate what CRUD functions should be implemented instead of implementing all, or forcing the user to define each in the Endpoints section
|
||||
- [ ] Support field transformations on Entities using a template for `BeforeToORM` in `service.pb.gorm.override.go`
|
||||
|
||||
## Design Philosophy (changeable...)
|
||||
|
||||
|
Reference in New Issue
Block a user