Initial commit: Add some basic CLI and Environment Var code
This commit is contained in:
parent
162863b4dd
commit
6a664613db
16 changed files with 1705 additions and 2 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@
|
|||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
target/**
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"args": ["--url=\\?test"],
|
||||
"program": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
116
README.md
116
README.md
|
@ -1,3 +1,115 @@
|
|||
# pilgrim
|
||||
# Pilgrim: An SQL Migration Tool Worth The Pilgrimage
|
||||
|
||||
A database migration tool written in golang
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
$ pilgrim --url 'mariadb://localhost:3306/database' --up
|
||||
$ pilgrim --url 'driver://user:pass@host:port/database?arg1=val1&arg2=val2' --dir ./migrations --up
|
||||
$ pilgrim --url 'postgres://localhost:5432/database' --down '2024-09-27/1_CreateTableABC.sql'
|
||||
$ pilgrim --driver 'driver' --username 'user' --password 'pass' --host 'host' --port 'port' --segments 'database' --args 'arg1=val1&arg2=val2'
|
||||
$ pilgrim --validate_migration_order --validate_latest
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The order of precedence for configured values is as follows:
|
||||
|
||||
1. Values provided in explicit cli arguments ( `--username`, `--password`, `--host`, `--port`, etc. )
|
||||
2. Values provided in cli `--url`
|
||||
3. Values provided in `PILGRIM_*` environment variables
|
||||
- Values in `PILGRIM_URL` are of lowest precedence and are overridden by their explicit `PILGRIM_*` equivalents
|
||||
|
||||
By default, pilgrim will search for a `.pilgrim` file in the directory it has been run in and will use the values within to replace values that have not been provided in a higher precedence form.
|
||||
|
||||
#### CLI Arguments
|
||||
|
||||
| Argument | Meaning |
|
||||
|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `--url` | The database url |
|
||||
| `--driver` | The database driver ( must be one of `mysql`, `mssql`, `postgres`, `mariadb`, `oracle`, or `sqlite` ) |
|
||||
| `--username` | The username to use |
|
||||
| `--password` | The password to use |
|
||||
| `--host` | The host/ip of the db server |
|
||||
| `--port` | The port of the db server |
|
||||
| `--segments` | The segments ( typically the db name ) |
|
||||
| `--arguments` | The arguments for the db server |
|
||||
| `--dir` | The directory of the db migrations |
|
||||
| `--down` | The migration to downgrade to ( conflicts with `--up` ). Defaults to `false` |
|
||||
| `--up` | The migration to upgrade to ( conflicts with `--down` ). Defaults to `false` |
|
||||
| `--script` | The migration script to upgrade/downgrade to. Can also be `latest`. ( Defaults to `latest` ) |
|
||||
| `--schema` | The migration table schema. Must exist prior to running migrations. Default value depends on database driver ( default schema ) |
|
||||
| `--table` | The migration table name. Default value is "pilgrim_migrations" |
|
||||
| `--disable_checksum_validation` | Disable checksum validation when running database migration |
|
||||
| `--strict_ordering` | Enforce strict ordering, pilgrim will run a validation of migration execution order before running the migrations |
|
||||
| `--validate_migration_order` | (Special) Runs a validation of the migration directory to ensure there is no uncertainty in migration order of execution. |
|
||||
| `--validate_latest` | (Special) Validate that the provided database contains all migrations in the migration directory |
|
||||
| `--validate_checksums` | (Special) Validate that all current checksums of migration files match the ones stored in the migration table |
|
||||
|
||||
##### Conflicts between `--down` and `--up`
|
||||
|
||||
If both `--down` and `--up` are provided, `pilgrim` will return an error and will not migrate the database.
|
||||
The `--script` name provided must adhere to the [migration naming convention and contain the folder it is located in](#migration-naming-convention--folder-structure).
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
| Variable | Meaning | Example |
|
||||
|-------------------|---------------------------------------------------------------------|-----------------------------------------------------------------------|
|
||||
| PILGRIM_URL | The database url | PILGRIM_URL=driver://user:pass@host:port/database?arg1=val1&arg2=val2 |
|
||||
| PILGRIM_DRIVER | The driver for the database | PILGRIM_DRIVER=postgres |
|
||||
| PILGRIM_USERNAME | The username to use | PILGRIM_USERNAME=user |
|
||||
| PILGRIM_PASSWORD | The password to use | PILGRIM_PASSWORD=pass |
|
||||
| PILGRIM_HOST | The host/ip of the db server | PILGRIM_HOST=localhost |
|
||||
| PILGRIM_PORT | The port of the db server | PILGRIM_PORT=9999 |
|
||||
| PILGRIM_SEGMENTS | The segments ( typically the db name ) | PILGRIM_SEGMENTS=database; PILGRIM_SEGMENTS=db/seg1 |
|
||||
| PILGRIM_ARGUMENTS | The arguments for the db server | PILGRIM_ARGUMENTS="arg1=val1&arg2=val2" |
|
||||
| PILGRIM_DIRECTORY | The directory of the db migrations | PILGRIM_DIRECTORY=./migrations |
|
||||
| PILGRIM_SCHEMA | The migration table schema. Must exist prior to running migrations. | PILGRIM_SCHEMA="public" |
|
||||
| PILGRIM_TABLE | The migration table. | PILGRIM_TABLE="pilgrim_migrations" |
|
||||
|
||||
#### Migration Naming Convention & Folder Structure
|
||||
|
||||
`pilgrim` requires that migration files be named uniquely, and can be ordered. To ensure this, migrations
|
||||
must be sorted by date, and then further within each date-named directory, the contained migration files must begin with a number.
|
||||
The numbers need not be unique ( you can have multiple scripts starting with `1_`, for example ), but the name of the migration does.
|
||||
|
||||
Example migration directory:
|
||||
|
||||
```
|
||||
migrations
|
||||
├─2024-09-28
|
||||
│ ├─1_CreateTableA.sql
|
||||
│ ├─2_CreateViewA.sql
|
||||
│ └─3_DropConstraintA.sql
|
||||
├─2024-10-01
|
||||
│ ├─1_CreateTableB.sql
|
||||
│ └─1_CreateTableC.sql
|
||||
└─2024-10-12
|
||||
└─...
|
||||
```
|
||||
|
||||
#### Migration Definition
|
||||
|
||||
Creating a pilgrim migration involves creating a regular SQL file, with some mandatory comments contained within:
|
||||
|
||||
```sql
|
||||
-- PILGRIM::UP
|
||||
|
||||
CREATE TABLE ...
|
||||
|
||||
-- PILGRIM::DOWN
|
||||
|
||||
DROP TABLE ...
|
||||
```
|
||||
|
||||
Everything between `-- PILGRIM::UP` and `-- PILGRIM::DOWN` is run during the `--up` phase of migration.
|
||||
Everything after `--PILGRIM::DOWN` to the end of the file is run during the `--down` phase of migration.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
This project is built using golang `1.23.1`.
|
||||
|
||||
1. Checkout this repository
|
||||
2. Run `go build -o target`
|
||||
|
||||
This will build a `pilgrim` binary in the `target` folder.
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module mvvasilev.dev/pilgrim
|
||||
|
||||
go 1.23.1
|
16
main.go
Normal file
16
main.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"mvvasilev.dev/pilgrim/pilgrim"
|
||||
)
|
||||
|
||||
func main() {
|
||||
pilgrimContext := pilgrim.NewPilgrimContext(
|
||||
pilgrim.CliContext(),
|
||||
pilgrim.NewEnvVarContext(),
|
||||
)
|
||||
|
||||
log.Println(pilgrimContext)
|
||||
}
|
279
pilgrim/cli.go
Normal file
279
pilgrim/cli.go
Normal file
|
@ -0,0 +1,279 @@
|
|||
package pilgrim
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
Flag_Url string = "url"
|
||||
Flag_Driver string = "driver"
|
||||
Flag_Username string = "username"
|
||||
Flag_Password string = "password"
|
||||
Flag_Host string = "host"
|
||||
Flag_Port string = "port"
|
||||
Flag_Segments string = "segments"
|
||||
Flag_Args string = "args"
|
||||
Flag_Directory string = "dir"
|
||||
Flag_Up string = "up"
|
||||
Flag_Down string = "down"
|
||||
Flag_Script string = "script"
|
||||
Flag_Schema string = "schema"
|
||||
Flag_Table string = "table"
|
||||
Flag_DisableChecksumValidation string = "disable_checksum_validation"
|
||||
Flag_StrictOrdering string = "strict_ordering"
|
||||
Flag_ValidateMigrationOrder string = "validate_migration_order"
|
||||
Flag_ValidateLatest string = "validate_latest"
|
||||
Flag_ValidateChecksums string = "validate_checksums"
|
||||
)
|
||||
|
||||
type CliFlagRetriever interface {
|
||||
Url() (value string, isProvided bool)
|
||||
Driver() (value DbDriver, isProvided bool)
|
||||
Username() (value string, isProvided bool)
|
||||
Password() (value string, isProvided bool)
|
||||
Host() (value string, isProvided bool)
|
||||
Port() (value string, isProvided bool)
|
||||
Segments() (value []string, isProvided bool)
|
||||
Args() (value map[string]string, isProvided bool)
|
||||
Directory() (value string, isProvided bool)
|
||||
IsUp() (value bool, isProvided bool)
|
||||
IsDown() (value bool, isProvided bool)
|
||||
// Can be a script name, or "latest"
|
||||
Script() (value string, isProvided bool)
|
||||
MigrationTable() (value string, isProvided bool)
|
||||
MigrationTableSchema() (value string, isProvided bool)
|
||||
IsChecksumValidationEnabled() (value bool, isProvided bool)
|
||||
IsStrictOrderingEnabled() (value bool, isProvided bool)
|
||||
IsValidatingMigrationOrder() (value bool, isProvided bool)
|
||||
IsValidatingLatest() (value bool, isProvided bool)
|
||||
IsValidatingChecksums() (value bool, isProvided bool)
|
||||
|
||||
ParseFlags()
|
||||
}
|
||||
|
||||
var ctxSingleton *cliContext
|
||||
var once sync.Once
|
||||
|
||||
type cliContext struct {
|
||||
isProvided map[string]bool
|
||||
|
||||
url,
|
||||
driver,
|
||||
username,
|
||||
password,
|
||||
host,
|
||||
port,
|
||||
segments,
|
||||
args,
|
||||
directory,
|
||||
script,
|
||||
migrationTable,
|
||||
migrationTableSchema *string
|
||||
|
||||
up,
|
||||
down,
|
||||
strictOrdering,
|
||||
disableChecksumValidation,
|
||||
validateChecksums,
|
||||
validateMigrationOrder,
|
||||
validateLatest *bool
|
||||
}
|
||||
|
||||
// Creates the cliContext singleton
|
||||
// Using any of the flags here requires a call to `ParseFlags`
|
||||
func CliContext() *cliContext {
|
||||
once.Do(func() {
|
||||
ctxSingleton = &cliContext{
|
||||
url: flag.String(Flag_Url, EmptyString, "<driver>://[[username]:[password]@]<host>:<port>[/database[?arg1=value[&arg2=value ...]]]"),
|
||||
driver: flag.String(Flag_Driver, EmptyString, "The database driver ( mysql, postgres, sqlite, mariadb, oracle, mssql )"),
|
||||
username: flag.String(Flag_Username, EmptyString, "The user"),
|
||||
password: flag.String(Flag_Password, EmptyString, "Password for user"),
|
||||
host: flag.String(Flag_Host, EmptyString, "The database host"),
|
||||
port: flag.String(Flag_Port, EmptyString, "The database port"),
|
||||
segments: flag.String(Flag_Segments, EmptyString, "The segments ( often the database name, in format \"segment1/segment2\" )"),
|
||||
args: flag.String(Flag_Args, EmptyString, "Additional arguments to provide the database connection ( in format 'arg1=value&arg2=value' )"),
|
||||
directory: flag.String(Flag_Directory, "./migrations", "The migration directory"),
|
||||
script: flag.String(Flag_Script, "latest", "The script to upgrade/downgrade to."),
|
||||
migrationTable: flag.String(Flag_Table, "pilgrim_migrations", "The database table to store migration information. WARNING: Changing this after running migrations once WILL NOT automagically move migration metadata from one table to another and will run all migrations from scratch."),
|
||||
migrationTableSchema: flag.String(Flag_Schema, "", "The schema the database table belongs to. Default depends on driver. See WARNING of migration table."),
|
||||
|
||||
up: flag.Bool(Flag_Up, false, "Upgrade the database."),
|
||||
down: flag.Bool(Flag_Down, false, "Upgrade the database."),
|
||||
strictOrdering: flag.Bool(Flag_StrictOrdering, false, "Enforce strict ordering, pilgrim will run a validation of migration execution order before running the migrations"),
|
||||
disableChecksumValidation: flag.Bool(Flag_DisableChecksumValidation, false, "Disable checksum validation when running database migration"),
|
||||
|
||||
validateChecksums: flag.Bool(Flag_ValidateChecksums, false, "(Special) Validate that all current checksums of migration files match the ones stored in the migration table"),
|
||||
validateMigrationOrder: flag.Bool(Flag_ValidateMigrationOrder, false, "(Special) Runs a validation of the migration directory to ensure there is no uncertainty in migration order of execution."),
|
||||
validateLatest: flag.Bool(Flag_ValidateLatest, false, "(Special) Validate that the provided database contains all migrations in the migration directory"),
|
||||
|
||||
isProvided: make(map[string]bool),
|
||||
}
|
||||
})
|
||||
|
||||
return ctxSingleton
|
||||
}
|
||||
|
||||
func (cli *cliContext) ParseFlags() {
|
||||
flag.Parse()
|
||||
|
||||
flag.Visit(func(f *flag.Flag) {
|
||||
cli.isProvided[f.Name] = true
|
||||
})
|
||||
}
|
||||
|
||||
func (cli *cliContext) Url() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Url)
|
||||
}
|
||||
|
||||
func (cli *cliContext) Driver() (value DbDriver, isProvided bool) {
|
||||
val, provided := cli.fetchStringFlagValue(Flag_Driver)
|
||||
|
||||
return DbDriver(val), provided
|
||||
}
|
||||
|
||||
func (cli *cliContext) Username() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Username)
|
||||
}
|
||||
|
||||
func (cli *cliContext) Password() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Password)
|
||||
}
|
||||
|
||||
func (cli *cliContext) Host() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Host)
|
||||
}
|
||||
|
||||
func (cli *cliContext) Port() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Port)
|
||||
}
|
||||
|
||||
func (cli *cliContext) Segments() (value []string, isProvided bool) {
|
||||
rawVal, provided := cli.fetchStringFlagValue(Flag_Segments)
|
||||
|
||||
if rawVal == EmptyString {
|
||||
return []string{}, false
|
||||
}
|
||||
|
||||
return ParseSegments(rawVal), provided
|
||||
}
|
||||
|
||||
func (cli *cliContext) Args() (value map[string]string, isProvided bool) {
|
||||
rawVal, provided := cli.fetchStringFlagValue(Flag_Args)
|
||||
|
||||
if rawVal == EmptyString {
|
||||
return make(map[string]string), false
|
||||
}
|
||||
|
||||
return ParseArguments(rawVal), provided
|
||||
}
|
||||
|
||||
func (cli *cliContext) Directory() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Directory)
|
||||
}
|
||||
|
||||
func (cli *cliContext) IsUp() (value bool, isProvided bool) {
|
||||
return cli.fetchBoolFlagValue(Flag_Up)
|
||||
}
|
||||
|
||||
func (cli *cliContext) IsDown() (value bool, isProvided bool) {
|
||||
return cli.fetchBoolFlagValue(Flag_Down)
|
||||
}
|
||||
|
||||
// Can be a script name, or "latest"
|
||||
func (cli *cliContext) Script() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Script)
|
||||
}
|
||||
|
||||
func (cli *cliContext) MigrationTable() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Table)
|
||||
}
|
||||
|
||||
func (cli *cliContext) MigrationTableSchema() (value string, isProvided bool) {
|
||||
return cli.fetchStringFlagValue(Flag_Schema)
|
||||
}
|
||||
|
||||
func (cli *cliContext) IsChecksumValidationEnabled() (value bool, isProvided bool) {
|
||||
disabled, provided := cli.fetchBoolFlagValue(Flag_DisableChecksumValidation)
|
||||
return !disabled, provided
|
||||
}
|
||||
|
||||
func (cli *cliContext) IsStrictOrderingEnabled() (value bool, isProvided bool) {
|
||||
return cli.fetchBoolFlagValue(Flag_StrictOrdering)
|
||||
}
|
||||
|
||||
func (cli *cliContext) IsValidatingMigrationOrder() (value bool, isProvided bool) {
|
||||
return cli.fetchBoolFlagValue(Flag_ValidateMigrationOrder)
|
||||
}
|
||||
|
||||
func (cli *cliContext) IsValidatingLatest() (value bool, isProvided bool) {
|
||||
return cli.fetchBoolFlagValue(Flag_ValidateLatest)
|
||||
}
|
||||
|
||||
func (cli *cliContext) IsValidatingChecksums() (value bool, isProvided bool) {
|
||||
return cli.fetchBoolFlagValue(Flag_ValidateChecksums)
|
||||
}
|
||||
|
||||
func (cli *cliContext) fetchStringFlagValue(f string) (value string, isProvided bool) {
|
||||
var valPtr *string = nil
|
||||
|
||||
switch f {
|
||||
case Flag_Url:
|
||||
valPtr = cli.url
|
||||
case Flag_Driver:
|
||||
valPtr = cli.driver
|
||||
case Flag_Username:
|
||||
valPtr = cli.username
|
||||
case Flag_Password:
|
||||
valPtr = cli.password
|
||||
case Flag_Host:
|
||||
valPtr = cli.host
|
||||
case Flag_Port:
|
||||
valPtr = cli.port
|
||||
case Flag_Segments:
|
||||
valPtr = cli.segments
|
||||
case Flag_Args:
|
||||
valPtr = cli.args
|
||||
case Flag_Directory:
|
||||
valPtr = cli.directory
|
||||
case Flag_Script:
|
||||
valPtr = cli.script
|
||||
case Flag_Schema:
|
||||
valPtr = cli.migrationTableSchema
|
||||
case Flag_Table:
|
||||
valPtr = cli.migrationTable
|
||||
}
|
||||
|
||||
if valPtr == nil {
|
||||
return EmptyString, cli.isProvided[f]
|
||||
}
|
||||
|
||||
return *valPtr, cli.isProvided[f]
|
||||
}
|
||||
|
||||
func (cli *cliContext) fetchBoolFlagValue(f string) (value bool, isProvided bool) {
|
||||
var valPtr *bool = nil
|
||||
|
||||
switch f {
|
||||
case Flag_Up:
|
||||
valPtr = cli.up
|
||||
case Flag_Down:
|
||||
valPtr = cli.down
|
||||
case Flag_StrictOrdering:
|
||||
valPtr = cli.strictOrdering
|
||||
case Flag_DisableChecksumValidation:
|
||||
valPtr = cli.disableChecksumValidation
|
||||
case Flag_ValidateChecksums:
|
||||
valPtr = cli.validateChecksums
|
||||
case Flag_ValidateMigrationOrder:
|
||||
valPtr = cli.validateMigrationOrder
|
||||
case Flag_ValidateLatest:
|
||||
valPtr = cli.validateLatest
|
||||
}
|
||||
|
||||
if valPtr == nil {
|
||||
return false, cli.isProvided[f]
|
||||
}
|
||||
|
||||
return *valPtr, cli.isProvided[f]
|
||||
}
|
213
pilgrim/context.go
Normal file
213
pilgrim/context.go
Normal file
|
@ -0,0 +1,213 @@
|
|||
package pilgrim
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
const DefaultMigrationDirectory = "./migrations"
|
||||
const DefaultMigrationTable = "pilgrim_migrations"
|
||||
|
||||
type MigrationPhase bool
|
||||
|
||||
const (
|
||||
MigrationPhase_Up MigrationPhase = true
|
||||
MigrationPhase_Down = false
|
||||
)
|
||||
|
||||
type PilgrimConfigurationRetriever interface {
|
||||
UrlParts() UrlParts
|
||||
MigrationDirectory() string
|
||||
MigrationPhase() MigrationPhase
|
||||
MigrationScript() string
|
||||
MigrationStrictOrdering() bool
|
||||
MigrationIsChecksumValidationEnabled() bool
|
||||
MigrationTable() string
|
||||
MigrationSchema() string
|
||||
|
||||
ValidateChecksums() bool
|
||||
ValidateLatest() bool
|
||||
ValidateMigrationOrder() bool
|
||||
}
|
||||
|
||||
type pilgrimContext struct {
|
||||
urlParts UrlParts
|
||||
migrationDirectory string
|
||||
migrationPhase MigrationPhase
|
||||
migrationScript string
|
||||
migrationStrictOrdering bool
|
||||
migrationIsChecksumValidationEnabled bool
|
||||
migrationSchema string
|
||||
migrationTable string
|
||||
|
||||
validateMigrationOrder bool
|
||||
validateLatest bool
|
||||
validateChecksums bool
|
||||
}
|
||||
|
||||
func NewPilgrimContext(cli CliFlagRetriever, env EnvVarRetriever) *pilgrimContext {
|
||||
|
||||
cliUrl, _ := cli.Url()
|
||||
cliUrlParts := ParseDatabaseUrl(cliUrl)
|
||||
envUrl, _ := env.Url()
|
||||
envUrlParts := ParseDatabaseUrl(envUrl)
|
||||
|
||||
urlParts := UrlParts{
|
||||
Driver: pickFirstAvailable(
|
||||
cli.Driver,
|
||||
func() (DbDriver, bool) { return DbDriver(cliUrlParts.Driver), cliUrlParts.Driver != EmptyString },
|
||||
env.Driver,
|
||||
func() (DbDriver, bool) { return DbDriver(envUrlParts.Driver), envUrlParts.Driver != EmptyString },
|
||||
),
|
||||
Username: pickFirstAvailable(
|
||||
cli.Username,
|
||||
func() (string, bool) { return cliUrlParts.Username, cliUrlParts.Username != EmptyString },
|
||||
env.Username,
|
||||
func() (string, bool) { return envUrlParts.Username, envUrlParts.Username != EmptyString },
|
||||
),
|
||||
Password: pickFirstAvailable(
|
||||
cli.Password,
|
||||
func() (string, bool) { return cliUrlParts.Password, cliUrlParts.Password != EmptyString },
|
||||
env.Password,
|
||||
func() (string, bool) { return envUrlParts.Password, envUrlParts.Password != EmptyString },
|
||||
),
|
||||
Host: pickFirstAvailable(
|
||||
cli.Host,
|
||||
func() (string, bool) { return cliUrlParts.Host, cliUrlParts.Host != EmptyString },
|
||||
env.Host,
|
||||
func() (string, bool) { return envUrlParts.Host, envUrlParts.Host != EmptyString },
|
||||
),
|
||||
Port: pickFirstAvailable(
|
||||
cli.Port,
|
||||
func() (string, bool) { return cliUrlParts.Port, cliUrlParts.Port != EmptyString },
|
||||
env.Port,
|
||||
func() (string, bool) { return envUrlParts.Port, envUrlParts.Port != EmptyString },
|
||||
),
|
||||
Segments: pickFirstAvailable(
|
||||
cli.Segments,
|
||||
func() ([]string, bool) { return cliUrlParts.Segments, cliUrlParts.Segments != nil },
|
||||
env.Segments,
|
||||
func() ([]string, bool) { return envUrlParts.Segments, envUrlParts.Segments != nil },
|
||||
),
|
||||
Arguments: pickFirstAvailable(
|
||||
cli.Args,
|
||||
func() (map[string]string, bool) { return cliUrlParts.Arguments, cliUrlParts.Arguments != nil },
|
||||
env.Args,
|
||||
func() (map[string]string, bool) { return envUrlParts.Arguments, envUrlParts.Arguments != nil },
|
||||
),
|
||||
}
|
||||
|
||||
isValid, err := urlParts.Validate()
|
||||
|
||||
if !isValid {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
migrationPhase, migrationScript, err := determineMigrationPhaseAndScript(cli)
|
||||
|
||||
migrationDirectory := pickFirstAvailable(cli.Directory, env.Directory, func() (string, bool) { return DefaultMigrationDirectory, true })
|
||||
migrationSchema := pickFirstAvailable(cli.MigrationTableSchema, env.MigrationTableSchema, pickFirstDefault(EmptyString)) // Default depends on driver, set empty for now
|
||||
migrationTable := pickFirstAvailable(cli.MigrationTable, env.MigrationTable, pickFirstDefault(DefaultMigrationTable))
|
||||
|
||||
migrationStrictOrdering, _ := cli.IsStrictOrderingEnabled()
|
||||
migrationIsChecksumValidationEnabled, _ := cli.IsChecksumValidationEnabled()
|
||||
|
||||
validateMigrationOrder, _ := cli.IsValidatingMigrationOrder()
|
||||
validateLatest, _ := cli.IsValidatingLatest()
|
||||
validateChecksums, _ := cli.IsValidatingLatest()
|
||||
|
||||
return &pilgrimContext{
|
||||
urlParts,
|
||||
migrationDirectory,
|
||||
migrationPhase,
|
||||
migrationScript,
|
||||
migrationStrictOrdering,
|
||||
migrationIsChecksumValidationEnabled,
|
||||
migrationSchema,
|
||||
migrationTable,
|
||||
validateMigrationOrder,
|
||||
validateLatest,
|
||||
validateChecksums,
|
||||
}
|
||||
}
|
||||
|
||||
func determineMigrationPhaseAndScript(cli CliFlagRetriever) (phase MigrationPhase, script string, err error) {
|
||||
script, _ = cli.Script()
|
||||
|
||||
isUp, isUpProvided := cli.IsUp()
|
||||
_, isDownProvided := cli.IsDown()
|
||||
|
||||
if (!isUpProvided && !isDownProvided) || (isUpProvided && isDownProvided) {
|
||||
return MigrationPhase_Down, EmptyString, PilgrimInvalidError("Must provide either --up or --down, but not both.")
|
||||
}
|
||||
|
||||
if isUp {
|
||||
phase = MigrationPhase_Up
|
||||
} else {
|
||||
phase = MigrationPhase_Down
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func pickFirstDefault[T any](defaultVal T) func() (value T, provided bool) {
|
||||
return func() (value T, provided bool) {
|
||||
return defaultVal, true
|
||||
}
|
||||
}
|
||||
|
||||
func pickFirstAvailable[T any](valueProviders ...func() (value T, provided bool)) (value T) {
|
||||
for _, getValue := range valueProviders {
|
||||
val, provided := getValue()
|
||||
|
||||
if provided {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
value, _ = valueProviders[len(valueProviders)-1]()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) UrlParts() UrlParts {
|
||||
return c.urlParts
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) MigrationDirectory() string {
|
||||
return c.migrationDirectory
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) MigrationPhase() MigrationPhase {
|
||||
return MigrationPhase(c.migrationPhase)
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) MigrationScript() string {
|
||||
return c.migrationScript
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) MigrationStrictOrdering() bool {
|
||||
return c.migrationStrictOrdering
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) MigrationIsChecksumValidationEnabled() bool {
|
||||
return c.migrationIsChecksumValidationEnabled
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) MigrationSchema() string {
|
||||
return c.migrationSchema
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) MigrationTable() string {
|
||||
return c.migrationTable
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) ValidateChecksums() bool {
|
||||
return c.validateChecksums
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) ValidateMigrationOrder() bool {
|
||||
return c.validateMigrationOrder
|
||||
}
|
||||
|
||||
func (c *pilgrimContext) ValidateLatest() bool {
|
||||
return c.validateLatest
|
||||
}
|
95
pilgrim/env.go
Normal file
95
pilgrim/env.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
package pilgrim
|
||||
|
||||
import "os"
|
||||
|
||||
const (
|
||||
EnvVarKey_Url string = "PILGRIM_URL"
|
||||
EnvVarKey_Driver = "PILGRIM_DRIVER"
|
||||
EnvVarKey_Username = "PILGRIM_USERNAME"
|
||||
EnvVarKey_Password = "PILGRIM_PASSWORD"
|
||||
EnvVarKey_Host = "PILGRIM_HOST"
|
||||
EnvVarKey_Port = "PILGRIM_PORT"
|
||||
EnvVarKey_Segments = "PILGRIM_SEGMENTS"
|
||||
EnvVarKey_Arguments = "PILGRIM_ARGUMENTS"
|
||||
EnvVarKey_Directory = "PILGRIM_DIRECTORY"
|
||||
EnvVarKey_Schema = "PILGRIM_SCHEMA"
|
||||
EnvVarKey_Table = "PILGRIM_TABLE"
|
||||
)
|
||||
|
||||
type EnvVarRetriever interface {
|
||||
Url() (value string, isDefined bool)
|
||||
Driver() (value DbDriver, isDefined bool)
|
||||
Username() (value string, isDefined bool)
|
||||
Password() (value string, isDefined bool)
|
||||
Host() (value string, isDefined bool)
|
||||
Port() (value string, isDefined bool)
|
||||
Segments() (value []string, isDefined bool)
|
||||
Args() (value map[string]string, isDefined bool)
|
||||
Directory() (value string, isDefined bool)
|
||||
MigrationTable() (value string, isDefined bool)
|
||||
MigrationTableSchema() (value string, isDefined bool)
|
||||
}
|
||||
|
||||
type envVarContext struct {
|
||||
}
|
||||
|
||||
func NewEnvVarContext() *envVarContext {
|
||||
return &envVarContext{}
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Url() (value string, isDefined bool) {
|
||||
return os.LookupEnv(EnvVarKey_Url)
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Driver() (value DbDriver, isDefined bool) {
|
||||
val, provided := os.LookupEnv(EnvVarKey_Driver)
|
||||
return DbDriver(val), provided
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Username() (value string, isDefined bool) {
|
||||
return os.LookupEnv(EnvVarKey_Username)
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Password() (value string, isDefined bool) {
|
||||
return os.LookupEnv(EnvVarKey_Password)
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Host() (value string, isDefined bool) {
|
||||
return os.LookupEnv(EnvVarKey_Host)
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Port() (value string, isDefined bool) {
|
||||
return os.LookupEnv(EnvVarKey_Port)
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Segments() (value []string, isDefined bool) {
|
||||
rawVal, provided := os.LookupEnv(EnvVarKey_Segments)
|
||||
|
||||
if !provided {
|
||||
return []string{}, false
|
||||
}
|
||||
|
||||
return ParseSegments(rawVal), true
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Args() (value map[string]string, isDefined bool) {
|
||||
rawVal, provided := os.LookupEnv(EnvVarKey_Arguments)
|
||||
|
||||
if !provided {
|
||||
return make(map[string]string), false
|
||||
}
|
||||
|
||||
return ParseArguments(rawVal), true
|
||||
}
|
||||
|
||||
func (ec *envVarContext) Directory() (value string, isDefined bool) {
|
||||
return os.LookupEnv(EnvVarKey_Directory)
|
||||
}
|
||||
|
||||
func (ec *envVarContext) MigrationTable() (value string, isDefined bool) {
|
||||
return os.LookupEnv(EnvVarKey_Table)
|
||||
}
|
||||
|
||||
func (ec *envVarContext) MigrationTableSchema() (value string, isDefined bool) {
|
||||
return os.LookupEnv(EnvVarKey_Schema)
|
||||
}
|
15
pilgrim/error.go
Normal file
15
pilgrim/error.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package pilgrim
|
||||
|
||||
type pilgrimValidationError struct {
|
||||
validationError string
|
||||
}
|
||||
|
||||
func PilgrimInvalidError(err string) *pilgrimValidationError {
|
||||
return &pilgrimValidationError{
|
||||
validationError: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (pve *pilgrimValidationError) Error() string {
|
||||
return pve.validationError
|
||||
}
|
226
pilgrim/test/cli_test.go
Normal file
226
pilgrim/test/cli_test.go
Normal file
|
@ -0,0 +1,226 @@
|
|||
package pilgrim_test
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"mvvasilev.dev/pilgrim/pilgrim"
|
||||
)
|
||||
|
||||
func Test_CliContext_Url(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) { return cli.Url() },
|
||||
pilgrim.Flag_Url,
|
||||
"driver://localhost:3306",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Driver(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (pilgrim.DbDriver, bool) { return cli.Driver() },
|
||||
pilgrim.Flag_Driver,
|
||||
"driver",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Username(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) { return cli.Username() },
|
||||
pilgrim.Flag_Username,
|
||||
"username",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Password(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) { return cli.Password() },
|
||||
pilgrim.Flag_Password,
|
||||
"password",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Host(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) { return cli.Host() },
|
||||
pilgrim.Flag_Host,
|
||||
"localhost",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Port(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) { return cli.Port() },
|
||||
pilgrim.Flag_Port,
|
||||
"3306",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Segments(t *testing.T) {
|
||||
expectedValue := []string{
|
||||
"segment1", "segment2", "segment3",
|
||||
}
|
||||
|
||||
flag.Set(pilgrim.Flag_Segments, "segment1/segment2/segment3")
|
||||
|
||||
value, _ := pilgrim.CliContext().Segments()
|
||||
|
||||
Assert(
|
||||
t,
|
||||
slices.Equal(value, expectedValue),
|
||||
"Cli value '", pilgrim.Flag_Segments, "' differs from expected.", "Value:", value, "Expected:", expectedValue,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Args(t *testing.T) {
|
||||
expectedValue := map[string]string{
|
||||
"arg1": "val1",
|
||||
"arg2": "val2",
|
||||
"arg3": "val3",
|
||||
}
|
||||
|
||||
flag.Set(pilgrim.Flag_Args, "arg1=val1&arg2=val2&arg3=val3")
|
||||
|
||||
value, _ := pilgrim.CliContext().Args()
|
||||
|
||||
Assert(
|
||||
t,
|
||||
maps.Equal(value, expectedValue),
|
||||
"Cli value '", pilgrim.Flag_Args, "' differs from expected.", "Value:", value, "Expected:", expectedValue,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Directory(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) { return cli.Directory() },
|
||||
pilgrim.Flag_Directory,
|
||||
"./custom-migrations",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Script(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) { return cli.Script() },
|
||||
pilgrim.Flag_Script,
|
||||
"2024-10-13/1_SomeScript.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_MigrationTable(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) { return cli.MigrationTable() },
|
||||
pilgrim.Flag_Table,
|
||||
"CustomMigrationsTable",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_MigrationTableSchema(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (string, bool) {
|
||||
return pilgrim.CliContext().MigrationTableSchema()
|
||||
},
|
||||
pilgrim.Flag_Schema,
|
||||
"public",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Up(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (bool, bool) { return cli.IsUp() },
|
||||
pilgrim.Flag_Up,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_Down(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (bool, bool) { return cli.IsDown() },
|
||||
pilgrim.Flag_Down,
|
||||
true,
|
||||
)
|
||||
}
|
||||
func Test_CliContext_StrictOrdering(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (bool, bool) {
|
||||
return pilgrim.CliContext().IsStrictOrderingEnabled()
|
||||
},
|
||||
pilgrim.Flag_StrictOrdering,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_DisableChecksumValidation(t *testing.T) {
|
||||
expectedValue := false
|
||||
|
||||
flag.Set(pilgrim.Flag_DisableChecksumValidation, "true")
|
||||
|
||||
value, _ := pilgrim.CliContext().IsChecksumValidationEnabled()
|
||||
|
||||
Assert(
|
||||
t,
|
||||
value == expectedValue,
|
||||
"Cli value '", pilgrim.Flag_DisableChecksumValidation, "' differs from expected.", "Value:", value, "Expected:", expectedValue,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_ValidateChecksums(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (bool, bool) {
|
||||
return cli.IsValidatingChecksums()
|
||||
},
|
||||
pilgrim.Flag_ValidateChecksums,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_ValidateMigrationOrder(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (bool, bool) {
|
||||
return cli.IsValidatingMigrationOrder()
|
||||
},
|
||||
pilgrim.Flag_ValidateMigrationOrder,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_CliContext_ValidateLatest(t *testing.T) {
|
||||
test_cliValue(
|
||||
t,
|
||||
func(cli pilgrim.CliFlagRetriever) (bool, bool) { return cli.IsValidatingLatest() },
|
||||
pilgrim.Flag_ValidateLatest,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func test_cliValue[T string | pilgrim.DbDriver | bool](t *testing.T, cliVal func(cli pilgrim.CliFlagRetriever) (value T, isProvided bool), flagName string, expectedVal T) {
|
||||
cli := pilgrim.CliContext()
|
||||
|
||||
flag.Set(flagName, fmt.Sprintf("%v", expectedVal))
|
||||
|
||||
cli.ParseFlags()
|
||||
|
||||
value, _ := cliVal(cli)
|
||||
|
||||
Assert(
|
||||
t,
|
||||
value == expectedVal,
|
||||
"Cli value '", flagName, "' differs from expected.", "Value:", value, "Expected:", expectedVal,
|
||||
)
|
||||
}
|
139
pilgrim/test/context_test.go
Normal file
139
pilgrim/test/context_test.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package pilgrim_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"mvvasilev.dev/pilgrim/pilgrim"
|
||||
pilgrim_mock "mvvasilev.dev/pilgrim/pilgrim/test/mock"
|
||||
)
|
||||
|
||||
const CliUrlValue = "postgres://cli_host:5432/cli_segment1/cli_segment2?cli_arg1=cli_val1&cli_arg2=cli_val2"
|
||||
const CliDriverValue = "postgres"
|
||||
|
||||
func createPilgrimContextWithDependencies(
|
||||
cliFlags map[string]string,
|
||||
envVars map[string]string,
|
||||
) (
|
||||
pilgrimContext pilgrim.PilgrimConfigurationRetriever,
|
||||
cliContext pilgrim.CliFlagRetriever,
|
||||
envContext pilgrim.EnvVarRetriever,
|
||||
) {
|
||||
cliStringFlagFuncs := map[string]func() (value string, isProvided bool){}
|
||||
|
||||
for k, v := range cliFlags {
|
||||
cliStringFlagFuncs[k] = func() (value string, isProvided bool) {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
cliContext = pilgrim_mock.MockCliContext(
|
||||
cliStringFlagFuncs,
|
||||
make(map[string]func() (value bool, isProvided bool)),
|
||||
func() (value []string, isProvided bool) { return []string{}, false },
|
||||
func() (value map[string]string, isProvided bool) { return map[string]string{}, false },
|
||||
)
|
||||
|
||||
envVarStringFuncs := map[string]func() (value string, isProvided bool){}
|
||||
|
||||
for k, v := range envVars {
|
||||
envVarStringFuncs[k] = func() (value string, isProvided bool) {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
envContext = pilgrim_mock.MockEnvVarContext(
|
||||
envVarStringFuncs,
|
||||
make(map[string]func() (value bool, isProvided bool)),
|
||||
func() (value []string, isProvided bool) { return []string{}, false },
|
||||
func() (value map[string]string, isProvided bool) { return map[string]string{}, false },
|
||||
)
|
||||
|
||||
pilgrimContext = pilgrim.NewPilgrimContext(cliContext, envContext)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Test_PilgrimContext_CliDriverOverridesCliUrlDriver(t *testing.T) {
|
||||
context, _, _ := createPilgrimContextWithDependencies(
|
||||
map[string]string{
|
||||
pilgrim.Flag_Url: CliUrlValue,
|
||||
pilgrim.Flag_Driver: CliDriverValue,
|
||||
},
|
||||
map[string]string{},
|
||||
)
|
||||
|
||||
AssertEquals(t, context.UrlParts().Driver, CliDriverValue)
|
||||
}
|
||||
|
||||
func Test_PilgrimContext_EnvDriverOverridesEnvUrlDriver(t *testing.T) {
|
||||
context, _, _ := createPilgrimContextWithDependencies(
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
pilgrim.EnvVarKey_Url: "mariadb://env_host:3306/env_segment1/env_segment2?env_arg1=env_val1&env_arg2=env_val2",
|
||||
pilgrim.EnvVarKey_Driver: "postgres",
|
||||
},
|
||||
)
|
||||
|
||||
AssertEquals(t, context.UrlParts().Driver, "postgres")
|
||||
}
|
||||
|
||||
func Test_PilgrimContext_CliUrlDriverOverridesEnvDriver(t *testing.T) {
|
||||
context, _, _ := createPilgrimContextWithDependencies(
|
||||
map[string]string{
|
||||
pilgrim.Flag_Url: CliUrlValue,
|
||||
},
|
||||
map[string]string{
|
||||
pilgrim.EnvVarKey_Driver: "mariadb",
|
||||
},
|
||||
)
|
||||
|
||||
AssertEquals(t, context.UrlParts().Driver, CliDriverValue)
|
||||
}
|
||||
|
||||
func Test_PilgrimContext_CliDriverOverridesEnvDriver(t *testing.T) {
|
||||
context, _, _ := createPilgrimContextWithDependencies(
|
||||
map[string]string{
|
||||
pilgrim.Flag_Driver: CliDriverValue,
|
||||
},
|
||||
map[string]string{
|
||||
pilgrim.EnvVarKey_Driver: "mariadb",
|
||||
},
|
||||
)
|
||||
|
||||
AssertEquals(t, context.UrlParts().Driver, CliDriverValue)
|
||||
}
|
||||
|
||||
func Test_PilgrimContext_EnvDriverIsUsedIfNoCliDriverProvided(t *testing.T) {
|
||||
context, _, _ := createPilgrimContextWithDependencies(
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
pilgrim.EnvVarKey_Driver: "mariadb",
|
||||
},
|
||||
)
|
||||
|
||||
AssertEquals(t, context.UrlParts().Driver, "mariadb")
|
||||
}
|
||||
|
||||
func Test_PilgrimContext_EnvUrlDriverIsUsedIfNoCliDriverProvided(t *testing.T) {
|
||||
context, _, _ := createPilgrimContextWithDependencies(
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
pilgrim.EnvVarKey_Url: "mariadb://env_host:3306/env_segment1/env_segment2?env_arg1=env_val1&env_arg2=env_val2",
|
||||
},
|
||||
)
|
||||
|
||||
AssertEquals(t, context.UrlParts().Driver, "mariadb")
|
||||
}
|
||||
|
||||
func Test_PilgrimContext_EnvUrlDriverIsNotUsedIfNoCliDriverOverrideProvided(t *testing.T) {
|
||||
context, _, _ := createPilgrimContextWithDependencies(
|
||||
map[string]string{
|
||||
pilgrim.Flag_Driver: "postgres",
|
||||
},
|
||||
map[string]string{
|
||||
pilgrim.EnvVarKey_Url: "mariadb://env_host:3306/env_segment1/env_segment2?env_arg1=env_val1&env_arg2=env_val2",
|
||||
},
|
||||
)
|
||||
|
||||
AssertEquals(t, context.UrlParts().Driver, "postgres")
|
||||
}
|
149
pilgrim/test/env_test.go
Normal file
149
pilgrim/test/env_test.go
Normal file
|
@ -0,0 +1,149 @@
|
|||
package pilgrim_test
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"os"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"mvvasilev.dev/pilgrim/pilgrim"
|
||||
)
|
||||
|
||||
func test_envVarContext_StringValue(
|
||||
t *testing.T,
|
||||
envVarKey string,
|
||||
getEnvVar func() (value string, isDefined bool),
|
||||
expectedVal string,
|
||||
) {
|
||||
os.Setenv(envVarKey, expectedVal)
|
||||
|
||||
val, isDefined := getEnvVar()
|
||||
|
||||
Assert(t, isDefined, "Env var '", envVarKey, "' was not defined when it should have been")
|
||||
Assert(
|
||||
t,
|
||||
val == expectedVal,
|
||||
"Env var value '", envVarKey, "' differs from expected.", "Value:", val, "Expected:", expectedVal,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Url(t *testing.T) {
|
||||
test_envVarContext_StringValue(
|
||||
t,
|
||||
pilgrim.EnvVarKey_Url,
|
||||
func() (string, bool) { return pilgrim.NewEnvVarContext().Url() },
|
||||
"driver://user:pass@host:port/segment1/segment2?arg1=val1&arg2=val2",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Driver(t *testing.T) {
|
||||
expectedVal := pilgrim.DbDriver_MariaDB
|
||||
|
||||
os.Setenv(pilgrim.EnvVarKey_Driver, string(pilgrim.DbDriver_MariaDB))
|
||||
|
||||
val, isDefined := pilgrim.NewEnvVarContext().Driver()
|
||||
|
||||
Assert(t, isDefined, "Env var '", pilgrim.EnvVarKey_Driver, "' was not defined when it should have been")
|
||||
Assert(
|
||||
t,
|
||||
val == expectedVal,
|
||||
"Env var value '", pilgrim.EnvVarKey_Driver, "' differs from expected.", "Value:", val, "Expected:", expectedVal,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Username(t *testing.T) {
|
||||
test_envVarContext_StringValue(
|
||||
t,
|
||||
pilgrim.EnvVarKey_Username,
|
||||
func() (string, bool) { return pilgrim.NewEnvVarContext().Username() },
|
||||
"username",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Password(t *testing.T) {
|
||||
test_envVarContext_StringValue(
|
||||
t,
|
||||
pilgrim.EnvVarKey_Password,
|
||||
func() (string, bool) { return pilgrim.NewEnvVarContext().Password() },
|
||||
"password",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Host(t *testing.T) {
|
||||
test_envVarContext_StringValue(
|
||||
t,
|
||||
pilgrim.EnvVarKey_Host,
|
||||
func() (string, bool) { return pilgrim.NewEnvVarContext().Host() },
|
||||
"localhost",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Port(t *testing.T) {
|
||||
test_envVarContext_StringValue(
|
||||
t,
|
||||
pilgrim.EnvVarKey_Port,
|
||||
func() (string, bool) { return pilgrim.NewEnvVarContext().Port() },
|
||||
"3306",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Directory(t *testing.T) {
|
||||
test_envVarContext_StringValue(
|
||||
t,
|
||||
pilgrim.EnvVarKey_Directory,
|
||||
func() (string, bool) { return pilgrim.NewEnvVarContext().Directory() },
|
||||
"./custom-directory/migrations",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_MigrationTable(t *testing.T) {
|
||||
test_envVarContext_StringValue(
|
||||
t,
|
||||
pilgrim.EnvVarKey_Table,
|
||||
func() (string, bool) { return pilgrim.NewEnvVarContext().MigrationTable() },
|
||||
"pilgrim_migrations_custom_table",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_MigrationTableSchema(t *testing.T) {
|
||||
test_envVarContext_StringValue(
|
||||
t,
|
||||
pilgrim.EnvVarKey_Schema,
|
||||
func() (string, bool) { return pilgrim.NewEnvVarContext().MigrationTableSchema() },
|
||||
"public",
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Segments(t *testing.T) {
|
||||
expectedVal := []string{"segment1", "segment2", "segment3"}
|
||||
|
||||
os.Setenv(pilgrim.EnvVarKey_Segments, "segment1/segment2/segment3")
|
||||
|
||||
val, isDefined := pilgrim.NewEnvVarContext().Segments()
|
||||
|
||||
Assert(t, isDefined, "Env var '", pilgrim.EnvVarKey_Segments, "' was not defined when it should have been")
|
||||
Assert(
|
||||
t,
|
||||
slices.Equal(expectedVal, val),
|
||||
"Env var value '", pilgrim.EnvVarKey_Segments, "' differs from expected.", "Value:", val, "Expected:", expectedVal,
|
||||
)
|
||||
}
|
||||
|
||||
func Test_envVarContext_Args(t *testing.T) {
|
||||
expectedVal := map[string]string{
|
||||
"arg1": "val1",
|
||||
"arg2": "val2",
|
||||
"arg3": "val3",
|
||||
}
|
||||
|
||||
os.Setenv(pilgrim.EnvVarKey_Arguments, "arg1=val1&arg2=val2&arg3=val3")
|
||||
|
||||
val, isDefined := pilgrim.NewEnvVarContext().Args()
|
||||
|
||||
Assert(t, isDefined, "Env var '", pilgrim.EnvVarKey_Arguments, "' was not defined when it should have been")
|
||||
Assert(
|
||||
t,
|
||||
maps.Equal(expectedVal, val),
|
||||
"Env var value '", pilgrim.EnvVarKey_Arguments, "' differs from expected.", "Value:", val, "Expected:", expectedVal,
|
||||
)
|
||||
}
|
207
pilgrim/test/mock/context_mock.go
Normal file
207
pilgrim/test/mock/context_mock.go
Normal file
|
@ -0,0 +1,207 @@
|
|||
package pilgrim_mock
|
||||
|
||||
import "mvvasilev.dev/pilgrim/pilgrim"
|
||||
|
||||
type cliContextMock struct {
|
||||
stringFlags map[string]func() (value string, isProvided bool)
|
||||
boolFlags map[string]func() (value bool, isProvided bool)
|
||||
segments func() (value []string, isProvided bool)
|
||||
args func() (value map[string]string, isProvided bool)
|
||||
}
|
||||
|
||||
func MockCliContext(
|
||||
stringFlags map[string]func() (value string, isProvided bool),
|
||||
boolFlags map[string]func() (value bool, isProvided bool),
|
||||
segments func() (value []string, isProvided bool),
|
||||
args func() (value map[string]string, isProvided bool),
|
||||
) *cliContextMock {
|
||||
return &cliContextMock{
|
||||
stringFlags, boolFlags, segments, args,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) stringVal(val string) (value string, isProvided bool) {
|
||||
retrieverFunc, available := mock.stringFlags[val]
|
||||
|
||||
if !available {
|
||||
return pilgrim.EmptyString, false
|
||||
}
|
||||
|
||||
return retrieverFunc()
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) boolVal(val string) (value bool, isProvided bool) {
|
||||
retrieverFunc, available := mock.boolFlags[val]
|
||||
|
||||
if !available {
|
||||
return false, false
|
||||
}
|
||||
|
||||
return retrieverFunc()
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Url() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Url)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Driver() (value pilgrim.DbDriver, isProvided bool) {
|
||||
val, provided := mock.stringVal(pilgrim.Flag_Driver)
|
||||
return pilgrim.DbDriver(val), provided
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Username() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Username)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Password() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Password)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Host() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Host)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Port() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Port)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Segments() (value []string, isProvided bool) {
|
||||
return mock.segments()
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Args() (value map[string]string, isProvided bool) {
|
||||
return mock.args()
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) Directory() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Directory)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) IsUp() (value bool, isProvided bool) {
|
||||
return mock.boolVal(pilgrim.Flag_Up)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) IsDown() (value bool, isProvided bool) {
|
||||
return mock.boolVal(pilgrim.Flag_Down)
|
||||
}
|
||||
|
||||
// Can be a script name, or "latest"
|
||||
func (mock *cliContextMock) Script() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Script)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) MigrationTable() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Table)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) MigrationTableSchema() (value string, isProvided bool) {
|
||||
return mock.stringVal(pilgrim.Flag_Schema)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) IsChecksumValidationEnabled() (value bool, isProvided bool) {
|
||||
value, isProvided = mock.boolVal(pilgrim.Flag_DisableChecksumValidation)
|
||||
return !value, isProvided
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) IsStrictOrderingEnabled() (value bool, isProvided bool) {
|
||||
return mock.boolVal(pilgrim.Flag_StrictOrdering)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) IsValidatingMigrationOrder() (value bool, isProvided bool) {
|
||||
return mock.boolVal(pilgrim.Flag_ValidateMigrationOrder)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) IsValidatingLatest() (value bool, isProvided bool) {
|
||||
return mock.boolVal(pilgrim.Flag_ValidateLatest)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) IsValidatingChecksums() (value bool, isProvided bool) {
|
||||
return mock.boolVal(pilgrim.Flag_ValidateChecksums)
|
||||
}
|
||||
|
||||
func (mock *cliContextMock) ParseFlags() {
|
||||
|
||||
}
|
||||
|
||||
type envVarContextMock struct {
|
||||
stringVars map[string]func() (value string, isProvided bool)
|
||||
boolVars map[string]func() (value bool, isProvided bool)
|
||||
segments func() (value []string, isProvided bool)
|
||||
args func() (value map[string]string, isProvided bool)
|
||||
}
|
||||
|
||||
func MockEnvVarContext(
|
||||
stringVars map[string]func() (value string, isProvided bool),
|
||||
boolVars map[string]func() (value bool, isProvided bool),
|
||||
segments func() (value []string, isProvided bool),
|
||||
args func() (value map[string]string, isProvided bool),
|
||||
) *envVarContextMock {
|
||||
return &envVarContextMock{
|
||||
stringVars, boolVars, segments, args,
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) stringVal(val string) (value string, isProvided bool) {
|
||||
retrieverFunc, available := mock.stringVars[val]
|
||||
|
||||
if !available {
|
||||
return pilgrim.EmptyString, false
|
||||
}
|
||||
|
||||
return retrieverFunc()
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) boolVal(val string) (value bool, isProvided bool) {
|
||||
retrieverFunc, available := mock.boolVars[val]
|
||||
|
||||
if !available {
|
||||
return false, false
|
||||
}
|
||||
|
||||
return retrieverFunc()
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Url() (value string, isDefined bool) {
|
||||
return mock.stringVal(pilgrim.EnvVarKey_Url)
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Driver() (value pilgrim.DbDriver, isDefined bool) {
|
||||
val, provided := mock.stringVal(pilgrim.Flag_Driver)
|
||||
return pilgrim.DbDriver(val), provided
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Username() (value string, isDefined bool) {
|
||||
return mock.stringVal(pilgrim.EnvVarKey_Username)
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Password() (value string, isDefined bool) {
|
||||
return mock.stringVal(pilgrim.EnvVarKey_Password)
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Host() (value string, isDefined bool) {
|
||||
return mock.stringVal(pilgrim.EnvVarKey_Host)
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Port() (value string, isDefined bool) {
|
||||
return mock.stringVal(pilgrim.EnvVarKey_Port)
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Segments() (value []string, isDefined bool) {
|
||||
return mock.segments()
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Args() (value map[string]string, isDefined bool) {
|
||||
return mock.args()
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) Directory() (value string, isDefined bool) {
|
||||
return mock.stringVal(pilgrim.EnvVarKey_Directory)
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) MigrationTable() (value string, isDefined bool) {
|
||||
return mock.stringVal(pilgrim.EnvVarKey_Table)
|
||||
}
|
||||
|
||||
func (mock *envVarContextMock) MigrationTableSchema() (value string, isDefined bool) {
|
||||
return mock.stringVal(pilgrim.EnvVarKey_Schema)
|
||||
}
|
66
pilgrim/test/url_parts_test.go
Normal file
66
pilgrim/test/url_parts_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package pilgrim_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"mvvasilev.dev/pilgrim/pilgrim"
|
||||
)
|
||||
|
||||
func Test_ParseDatabaseUrl(t *testing.T) {
|
||||
type args struct {
|
||||
cliUrl string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want pilgrim.UrlParts
|
||||
}{
|
||||
{
|
||||
name: "parses_url_correctly",
|
||||
args: args{
|
||||
cliUrl: "driver://user:pass@localhost:9999/segment1/segment2?arg1=val1&arg2=val2",
|
||||
},
|
||||
want: pilgrim.UrlParts{
|
||||
Driver: "driver",
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
Host: "localhost",
|
||||
Port: "9999",
|
||||
Segments: []string{
|
||||
"segment1",
|
||||
"segment2",
|
||||
},
|
||||
Arguments: map[string]string{
|
||||
"arg1": "val1",
|
||||
"arg2": "val2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := pilgrim.ParseDatabaseUrl(tt.args.cliUrl); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ParseDatabaseUrl() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_UrlParts_Validate_ValidUrl(t *testing.T) {
|
||||
urlParts := pilgrim.ParseDatabaseUrl("mssql://user:pass@localhost:9999/segment1/segment2?arg1=val1&arg2=val2")
|
||||
isValid, err := urlParts.Validate()
|
||||
|
||||
Assert(t, isValid, "Valid DatabaseUrl returned as invalid", err)
|
||||
Assert(t, err == nil, "Valid DatabaseUrl returned validation errors", err)
|
||||
}
|
||||
|
||||
func Test_UrlParts_Validate_InvalidUrl(t *testing.T) {
|
||||
urlParts := pilgrim.ParseDatabaseUrl("invalid-driver://user:pass@localhost:9999/segment1/segment2?arg1=val1&arg2=val2")
|
||||
isValid, err := urlParts.Validate()
|
||||
|
||||
Assert(t, !isValid, "Invalid DatabaseUrl returned as valid", err)
|
||||
Assert(t, err != nil, "Invalid DatabaseUrl did not return validation errors", err)
|
||||
}
|
24
pilgrim/test/utils_test.go
Normal file
24
pilgrim/test/utils_test.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package pilgrim_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func Assert(t *testing.T, condition bool, errItems ...any) {
|
||||
if !condition {
|
||||
t.Fail()
|
||||
t.Log("Test failure reason:", errItems)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertEquals[T comparable](t *testing.T, value T, expected T) {
|
||||
if value != expected {
|
||||
t.Fail()
|
||||
t.Logf("Got %v, expected %v", value, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertEqualsFunc[T any](t *testing.T, value T, expected T, equalsFunc func(v, e T) bool) {
|
||||
if !equalsFunc(value, expected) {
|
||||
t.Fail()
|
||||
t.Logf("Got %v, expected %v", value, expected)
|
||||
}
|
||||
}
|
142
pilgrim/url_parts.go
Normal file
142
pilgrim/url_parts.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
package pilgrim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const EmptyString = ""
|
||||
|
||||
type DbDriver string
|
||||
|
||||
const (
|
||||
DbDriver_MySQL DbDriver = "mysql"
|
||||
DbDriver_MSSQL DbDriver = "mssql"
|
||||
DbDriver_Postgres DbDriver = "postgres"
|
||||
DbDriver_Oracle DbDriver = "oracle"
|
||||
DbDriver_SQLite DbDriver = "sqlite"
|
||||
DbDriver_MariaDB DbDriver = "mariadb"
|
||||
)
|
||||
|
||||
var AvailableDbDrivers []DbDriver = []DbDriver{
|
||||
DbDriver_MySQL,
|
||||
DbDriver_MSSQL,
|
||||
DbDriver_Postgres,
|
||||
DbDriver_Oracle,
|
||||
DbDriver_SQLite,
|
||||
DbDriver_MariaDB,
|
||||
}
|
||||
|
||||
type UrlParts struct {
|
||||
Host,
|
||||
Port,
|
||||
Username,
|
||||
Password string
|
||||
|
||||
Driver DbDriver
|
||||
|
||||
Segments []string
|
||||
Arguments map[string]string
|
||||
}
|
||||
|
||||
const DbUrlRegex = `^(?:([^:\/?#\s]+):\/{2})?(?:([^@\/?#\s]+)@)?([^\/?#\s]+)?(?:\/([^?#\s]*))?(?:[?]([^#\s]+))?\S*$`
|
||||
|
||||
func ParseDatabaseUrl(url string) UrlParts {
|
||||
urlRegex, err := regexp.Compile(DbUrlRegex)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Regex %s could not be compiled", DbUrlRegex) // The developer wrote the regex wrong.
|
||||
}
|
||||
|
||||
parsed := urlRegex.FindAllStringSubmatch(url, -1)
|
||||
|
||||
if parsed == nil || parsed[0] == nil {
|
||||
return UrlParts{}
|
||||
}
|
||||
|
||||
parsedUrl := parsed[0]
|
||||
|
||||
driver := DbDriver(parsedUrl[1])
|
||||
username := ""
|
||||
password := ""
|
||||
host := ""
|
||||
port := ""
|
||||
var segments []string
|
||||
var arguments map[string]string = make(map[string]string)
|
||||
|
||||
// Parse username and password ( username:password )
|
||||
if parsedUrl[2] != EmptyString && strings.Contains(parsedUrl[2], ":") {
|
||||
splitUserAndPass := strings.Split(parsedUrl[2], ":")
|
||||
username = splitUserAndPass[0]
|
||||
password = splitUserAndPass[1]
|
||||
}
|
||||
|
||||
// Parse host and port ( host:port )
|
||||
if parsedUrl[3] != EmptyString && strings.Contains(parsedUrl[3], ":") {
|
||||
splitHostAndPort := strings.Split(parsedUrl[3], ":")
|
||||
host = splitHostAndPort[0]
|
||||
port = splitHostAndPort[1]
|
||||
}
|
||||
|
||||
// Parse segments ( segment1/segment2 )
|
||||
if parsedUrl[4] != EmptyString {
|
||||
segments = ParseSegments(parsedUrl[4])
|
||||
}
|
||||
|
||||
// Parse arguments ( arg1=value1&arg2=value2 )
|
||||
if parsedUrl[5] != EmptyString {
|
||||
arguments = ParseArguments(parsedUrl[5])
|
||||
}
|
||||
|
||||
return UrlParts{
|
||||
Driver: driver,
|
||||
Username: username,
|
||||
Password: password,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Segments: segments,
|
||||
Arguments: arguments,
|
||||
}
|
||||
}
|
||||
|
||||
func ParseSegments(segments string) []string {
|
||||
if segments == EmptyString {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return strings.Split(segments, "/")
|
||||
}
|
||||
|
||||
func ParseArguments(arguments string) map[string]string {
|
||||
if arguments == EmptyString {
|
||||
return make(map[string]string)
|
||||
}
|
||||
|
||||
var argsMap map[string]string = make(map[string]string)
|
||||
|
||||
splitArguments := strings.Split(arguments, "&")
|
||||
|
||||
for _, arg := range splitArguments {
|
||||
if !strings.Contains(arg, "=") {
|
||||
continue
|
||||
}
|
||||
|
||||
splitArgAndValue := strings.Split(arg, "=")
|
||||
|
||||
argsMap[splitArgAndValue[0]] = splitArgAndValue[1]
|
||||
}
|
||||
|
||||
return argsMap
|
||||
}
|
||||
|
||||
// Returns true if valid, with nil error, or false if invalid, with the validation error
|
||||
func (urlParts *UrlParts) Validate() (bool, error) {
|
||||
if !slices.Contains(AvailableDbDrivers, DbDriver(urlParts.Driver)) {
|
||||
return false, PilgrimInvalidError(fmt.Sprintf("URLParts invalid: Provided driver %v is not currently supported ( must be one of %v )", urlParts.Driver, AvailableDbDrivers))
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
Loading…
Add table
Reference in a new issue