Documentation ¶
Overview ¶
Package cfg1 makes it possible to load configuration settings with version 1.x.y since all minor and patch versions (which are known) with the same major version, can be loaded with one implementation. When trying to serialize and write out settings, the latest known minor and patch version will be used since older versions (with the same major version) can ignore the extra fields too.
Index ¶
- Constants
- Variables
- type Cars
- type Config
- func (c *Config) Bounds() (minb, maxb *Serializable)
- func (c *Config) Clone() *Config
- func (c *Config) ConnectionInfo() (dbName, host string, port int)
- func (c *Config) ConnectionPool(ctx context.Context, r repo.Role) (repo.Pool, error)
- func (c *Config) Dereference() *Config
- func (c *Config) MajorVersion() uint
- func (c *Config) Marshal() *Marshalled
- func (c *Config) MarshalYAML() (interface{}, error)
- func (c *Config) MergeConfig(ctx context.Context, c2 *Config) error
- func (c *Config) Mutate(s Serializable) error
- func (c *Config) NewSchemaRepo() repo.Schema
- func (c *Config) RenewPasswords(ctx context.Context, ...) (finalizer func() error, err error)
- func (c *Config) SchemaInitializer(tx repo.Tx) (repo.SchemaInitializer, error)
- func (c *Config) SchemaMigrator(tx repo.Tx) (repo.Migrator[repo.SchemaSettler], error)
- func (c *Config) SchemaVersion() model.SemVer
- func (c *Config) Serializable() *Serializable
- func (c *Config) SetSchemaVersion(sv model.SemVer)
- func (c *Config) SettingsPersister(tx repo.Tx) (repo.SettingsPersister, error)
- func (c *Config) ValidateAndNormalize() error
- func (c *Config) Version() model.SemVer
- func (c *Config) Visible() *Visible
- type Database
- func (d Database) ConnectionInfo() (dbName, host string, port int)
- func (d Database) ConnectionPool(ctx context.Context, r repo.Role) (repo.Pool, error)
- func (d Database) ConnectionURL(r repo.Role, path string) (string, error)
- func (d Database) NewSchemaRepo() repo.Schema
- func (d Database) RenewPasswords(ctx context.Context, ...) (finalizer func() error, err error)
- func (d Database) SchemaMigrator(tx repo.Tx, srcDBVer model.SemVer) (repo.Migrator[repo.SchemaSettler], error)
- func (d *Database) ValidateAndNormalize() error
- type Gin
- type Immutable
- type Marshalled
- type OutOfBoundsSettingsError
- type Serializable
- type Settings
- type Usecases
- type Visible
Constants ¶
const ( Major = 1 Minor = 1 Patch = 0 )
These constants define the major, minor, and patch version of the configuration settings which are supported by the Config struct.
Variables ¶
Version is the semantic version of Config struct.
Functions ¶
This section is empty.
Types ¶
type Cars ¶
type Cars struct { // OldParkingDelay indicates the amount of delay that an old // parking method should incur. // A nil value indicates that delay is left uninitialized, so the // use cases layer may select a default value. OldParkingDelay *settings.Duration `yaml:"old-parking-method-delay"` // MinOldParkingDelay is the inclusive minimum acceptable value // for the OldParkingDelay setting. // A missing value indicates that there is no lower bound. MinOldParkingDelay *settings.Duration `yaml:"old-parking-method-delay-minimum"` // MaxOldParkingDelay is the inclusive maximum acceptable value // for the OldParkingDelay setting. // A missing value indicates that there is no upper bound. MaxOldParkingDelay *settings.Duration `yaml:"old-parking-method-delay-maximum"` }
Cars contains the configuration settings for the cars use cases. Fields are defined as pointers, so it is possible to detect if they are or are not initialized. After migrating from some configuration settings version, some settings may be left uninitialized because they may have no corresponding items in the source settings version. Those items can be detected as nil pointers and filled by their default values using the MergeConfig method.
type Config ¶
type Config struct { Database Database // PostgreSQL database connection settings Gin Gin // Gin-Gonic instantiation settings Usecases Usecases // Configuration settings for supported use cases // Vers contains the configuration file and database schema version // strings corresponding to this Config instance and its Database // target. Vers vers.Config `yaml:",inline"` // Comments contains the YAML comment lines which are written right // before the actual settings lines, aka head-comments. // These comments are preserved for top-level settings and their // children sequence and mapping YAML nodes. The Comments may be nil // which will be ignored, or may be poppulated with some comments // which will be preserved during a marshaling operation by the // multi-database migration operation. Indeed, Comments field is // only useful when the destination configuration file is loaded // during a migration operation because the MergeConfig method // preserves the destination Comments field, so the new comments // may be seen in the target config file. Comments *comment.Comment `yaml:"-"` }
Config contains all settings which are required by different parts of the project following the v1.x.y format, such as adapters or use cases. It is preferred to implement Config with primitive fields or other structs which are defined locally, not models or structs which are defined in lower layers, so the configuration can be versioned and kept intact while other layers can change freely. This version (when freezed and no further minor or patch release of it was supposed acceptable) may be embedded by the future config versions (if they need to copy some parts of this config version).
func Load ¶
Load unmarshals the data byte slice and loads a Config instance assuming that it contains the Config settings. Extra items in the data will be ignored and missing items will take their default values. Thereafter, loaded Config will be validated and normalized in order to ensure that provided settings are acceptable (for example the major version which is reported by data settings must match with number 1 which is the major version of this config package).
If some settings should be overridden by environment variables, this method is the proper place for that replacement. However, if settings should be overridden by some information from the database, they must not be replaced here because the Load method provides those settings which are fixed by each execution (while the database contents may change continually and their loading must be performed by a separate method, such as LoadFromDB).
func LoadFromDB ¶ added in v1.2.0
LoadFromDB parses the given data byte slice and loads a Config instance (the first return value). It also tries to establish a connection to the corresponding database which its connection information are described in the loaded Config instance. It is expected to find a serialized version of mutable settings following the same format which is used by Config (i.e., Serializable struct) in the database. The mutable settings from the database will override the settings which are read from the data byte slice. Thereafter, loaded and mutated Config will be validated and normalized in order to ensure that provided settings are acceptable.
If some settings should be overridden by environment variables, they should be updated after parsing the data byte slice and before checking the database contents (so configuration file may be updated by environment variables and both may be updated by database contents respectively). If an error prevents the configuration settings to be updated using the database contents, but the loaded static settings were valid themselves, LoadFromDB still returns the Config instance. The second return value which is a boolean reports if the Config instance is or is not being returned (like an ok flag for the first return value). Any errors will be returned as the last return value.
func (*Config) Bounds ¶ added in v1.3.0
func (c *Config) Bounds() (minb, maxb *Serializable)
Bounds creates and returns two instances of *Serializable in order to report the minimum and maximum boundary values for those settings which their lower/upper limits should be restricted. The boundary values may be reported for both of the mutable and immutable settings (as they have an informational purpose). All boundary values are obtained from this Config instance.
func (*Config) Clone ¶
Clone creates a new instance of Config and initializes its fields based on the `c` fields. Pointers are renewed too, so changes in the returned Config instance and `c` stay independent.
func (*Config) ConnectionInfo ¶
ConnectionInfo returns the host, port, and database name of the connection information which are kept in this Config instance.
func (*Config) ConnectionPool ¶
ConnectionPool creates a database connection pool using the connection information which are kept in the `c` settings.
func (*Config) Dereference ¶
Dereference returns the `c` Config instance itself.
Methods of the Config struct refer to other types based on this package Major version for complete type-safety. For example, the MergeConfig only accepts an instance of Config from this package and passing a cfg2.Config instance will be rejected at compile time. However, the use cases layer which does not know about the config version at compile time has to receive Config as an abstract interface which is common among all config versions. That abstract interface is defined as pkg/core/usecase/migrationuc.Settings which provides MergeSettings method instead of MergeConfig and accepts an instance of Settings interface instead of the Config instance. The pkg/adapter/config/settings.Adapter[Config, Serializable] is defined in order to wrap a Config instance and implement the migrationuc.Settings interface.
Presence of the Dereference method allows users of the Config struct and the Adapter[Config, Serializable] struct to use them uniformly. Indeed, both of the raw Config and its wrapper Adapter instances can be represented by pkg/adapter/config/settings.Dereferencer[Config] interface and so the wrapped Config instance may be obtained from them using the Dereference method. Note that a type assertion from the Settings interface to the Adapter instance requires pre-knowledge about the Adapter (and a Settings interface which is provided by some other adapter implementation may not be supported), while the Dereferencer[Config] interface can be provided by any adapter implementation simply by embedding the Config instance.
func (*Config) MajorVersion ¶
MajorVersion returns the major semantic version of this Config instance. This value matches with the first component of the version which is returned by the Version method. However, the Version method returns the complete semantic version as written in a configuration file, hence, it cannot be called without creating an instance of Config first. In contrast, this method only depends on the Config type and so can be called with a nil instance too.
func (*Config) Marshal ¶
func (c *Config) Marshal() *Marshalled
Marshal creates an instance of the Marshalled struct and fills it with the `c` Config instance contents. The Marshalled and Config fields do correspond with each other with one difference. Any field which requires a specific MarshalYAML logic (and its default encoding logic into YAML format is not suitable) is replaced by a primitive data type, so it can contain the properly serialized version of that field.
This Marshal method encodes and replaces fields which are defined in this package and recursively calls Marshal method on those fields which are defined in other packages. Therefore, the marshaling logic can be distributed among packages, near to the relevant data types (while MarshalYAML from the yaml.Marshaler interface is only called for the top-most object and is ignored for nested types).
func (*Config) MarshalYAML ¶
MarshalYAML computes an instance of the Marshalled struct, as created by the Marshal method, so it may be marshalled instead of the `c` Config instance. This replacement makes it possible to substitute specific settings such as a slices of numbers in a vers.Config with their alternative primitive data types and have control on the final serialization result. Thereafter, it encodes *Marshalled as a yaml node instance and saves the preserved head `c.Comments` (if any) into the resulting *yaml.Node instance (and returns it as an interface{}).
See the Marshal function for the reification details and how marshaling logic can be distributed among nested Config structs.
func (*Config) MergeConfig ¶
MergeConfig overwrites all fields of `c` which are not initialized (and have nil value) with their corresponding values from `c2` arg. The `c` config version will be set to the latest known version values as specified by Major, Minor, and Patch constants in this package. All database settings in `c` are overwritten by the `c2` values unconditionally. The database version number will be set to its latest supported version too, having the same major version as specified in `c2` instance. The Comments field takes its value from the `c2` instance, ignoring comments of the `c` instance (if any). Similarly, the boundary values are copied from the `c2` because the target boundary values should be respected after migration. By the way, settings may fail to fit in the expected range of boundary values. In this case, they will take the nearest (minimum/maximum) value and the violated boundaries will be logged as warning.
func (*Config) Mutate ¶ added in v1.2.0
func (c *Config) Mutate(s Serializable) error
Mutate updates this Config instance using the given Serializable instance which provides the mutable settings values. The given Serializable instance may contain mutable & invisible settings (write-only) and mutable & visible settings (read-write), but it may not contain the immutable settings (i.e., the Immutable pointer must be nil). The provided Serializable instance is not updated itself, hence, a non-pointer variable is suitable.
If provided values do not respect the expected boundary values, an error will be returned, indicating that which settings were out of bound, however, this type of error does not prevent this Config instance to be updated. When a minimum/maximum boundary value is crossed over, that boundary value itself will be used as the new value of that setting. In this scenario, returned error will have the *OutOfBoundsSettingsError type.
func (*Config) NewSchemaRepo ¶
NewSchemaRepo instantiates a fresh Schema repository. Role names may be optionally suffixed based on the settings and in that case, repo.Role role names which are passed to the ConnectionPool method or RenewPasswords will be suffixed automatically. Since the Schema repository has methods for creation of roles or asking to grant specific privileges to them, it needs to obtain the same role name suffix (as stored in the current SchemaSettings instance).
func (*Config) RenewPasswords ¶
func (c *Config) RenewPasswords( ctx context.Context, change func( ctx context.Context, roles []repo.Role, passwords []string, ) error, roles ...repo.Role, ) (finalizer func() error, err error)
RenewPasswords generates new secure passwords for the given roles and after recording them in a temporary file, will use the change function in order to update the passwords of those roles in the database too. The change function argument should perform the update operation in a transaction which may or may not be committed when RenewPasswords returns. In case of a successful commitment, the temporary passwords file should be moved over the main passwords file. The temporary passwords file is named as .pgpass.new and the main passwords file is named as .pgpass in this version. Keeping the .pgpass file (in the `c.Database.PassDir`) up-to-date, makes it possible to use ConnectionPool method again (both if the passwords are or are not updated successfully). This final file movement can be performed using the returned finalizer function.
func (*Config) SchemaInitializer ¶
SchemaInitializer creates a repo.SchemaInitializer instance which wraps the given transaction argument and can be used to initialize the database with development or production suitable data. The format of the created tables and their initial data rows are chosen based on the database schema version, as indicated by SchemaVersion method. All table creation and data insertion operations will be performed in the given transaction and will be persisted only if that transaction could commit successfully.
func (*Config) SchemaMigrator ¶
SchemaMigrator creates a repo.Migrator[repo.SchemaSettler] instance which wraps the given `tx` transaction argument and can be used for
- loading the source database schema information with this assumption that tx belongs to the destination database and this Config instance contains the source database connection information, so it can modify the destination database within a given transaction and fill a schema with tables which represent the source database contents (not moving data items necessarily, but may create them as a foreign data wrapper, aka FDW),
- creating upwards or downwards migrator objects in order to transform the loaded data into their upper/lower schema versions, again with minimal data transfer and using views instead of tables as far as possible, while creating tables or even loading data into this Golang process if it is necessary, and at last
- obtaining a repo.SchemaSettler instance for the target schema major version, so it can persist the target schema version by creating tables and filling them with contents of the corresponding views.
func (*Config) SchemaVersion ¶
SchemaVersion returns the semantic version of the database schema which its connection information are kept by this Config struct. There is no direct dependency between the configuration file and database schema versions.
func (*Config) Serializable ¶ added in v1.2.0
func (c *Config) Serializable() *Serializable
Serializable creates and returns an instance of *Serializable in order to report the mutable settings, based on this Config instance. The Immutable pointer will be nil in the returned object.
func (*Config) SetSchemaVersion ¶
SetSchemaVersion updates the semantic version of the database schema as recorded in this Config instance and reported by the SchemaVersion method.
func (*Config) SettingsPersister ¶ added in v1.2.0
SettingsPersister instantiates a repo.SettingsPersister for the database schema version of the `c` Config instance, wrapping the given `tx` transaction argument. Obtained settings persister depends on the schema major version alone because the migration process only needs to create and fill tables for the latest minor version of some target major version. Caller needs to serialize the mutable settings independently (based on the settings format version) and then employ this persister object for its storage in the database (see the settings.Adapter.Serialize and Config.Serializable methods). A transaction (not a connection) is required because other migration operations must be performed usually in the same transaction.
func (*Config) ValidateAndNormalize ¶
ValidateAndNormalize validates the configuration settings and returns an error if they were not acceptable. It can also modify settings in order to normalize them or replace some zero values with their expected default values (if any).
func (*Config) Version ¶
Version returns the semantic version of this Config struct contents which its major version is equal to 1, while its minor and patch versions may correspond to the Minor and Patch constants or may describe an older version (if the minor version of the returned semantic version was more recent than Minor constant, it could not be loaded by the Load function). By the way, no constraint exists on the patch version because it has no visible effect.
func (*Config) Visible ¶ added in v1.2.0
Visible creates and fills an instance of Visible struct with the mutable and immutable settings which can be queried by end-users. That is, the Immutable pointer will be non-nil in the returned object. Despite the Mutate and Serializable methods, the Visible method is not included in the pkg/adapter/config/settings.Config generic interface because it is only useful in the adapters layer where a repository package may query the visible settings after updating a Config instance. However, it is not required in the migration use cases as they deal with mutable settings which are exposed by the Serializable method.
type Database ¶
type Database struct { Host string // domain name or IP address of the DBMS server Port int // port number of the DBMS server Name string // database name, like caweb1_0_0 PassDir string `yaml:"pass-dir"` // path of the passwords dir // RoleSuffix specifies a possibly empty suffix for the database // role names. Normally, repo.AdminRole and repo.NormalRole roles // are used. In the parallel test cases, it is required to create // multiple non-colliding roles in the same database cluster and // so having a unique (per test) role suffix helps with parallelism. RoleSuffix repo.Role `yaml:"role-suffix,omitempty"` // AuthMethod specifies the database authentication method name. // This method indicates how passwords should be hashed and stored // in the database, so they may be used by an authentication // operation successfully. // Currently, only scram-sha-1 and scram-sha-256 methods are // supported. The scram-sha-256 is the default value. AuthMethod string `yaml:"auth-method,omitempty"` // contains filtered or unexported fields }
Database contains the database related configuration settings.
func (Database) ConnectionInfo ¶
ConnectionInfo returns the host, port, and database name of the connection information which are kept in this Database instance.
func (Database) ConnectionPool ¶
ConnectionPool creates a database connection pool using the connection information which are kept in the `d` settings. Initially, the .pgpass file in the d.PassDir folder is checked which should conform with the pgpass format with lines like this:
host:port:dbname:role:password
If a database connection could be established, created pool and nil error will be returned. Otherwise, passwords might have been updated during a previous incomplete migration operation. So the .pgpass.new file in the same d.PassDir folder is checked too. If a connection could be established successfully, the .pgpass.new will be moved to the .pgpass file, so the .pgpass.new file may be overwritten safely by the subsequent migration operations.
The `d.RoleSuffix` will be appended to the given `r` role name too.
func (Database) ConnectionURL ¶
ConnectionURL returns the database connection URL embedding the host, port, role name, database name, and password value. These items are directly taken from the `d` settings, but the role name which is specified by the `r` argument and the password value which is read from the given `path` file. Returned URL has the postgresql scheme. The `path` file may contain empty or `#`-commented lines in addition to the password specifying lines which should conform with the pgpass files format with lines like this:
host:port:dbname:role:password
If the `path` file could be read and a password for the asked `r` role could be identified, a URL and a nil error will be returned. Otherwise, returned string will be empty and error will describe the wrapped error condition.
func (Database) NewSchemaRepo ¶
NewSchemaRepo instantiates a fresh Schema repository. Role names may be optionally suffixed based on the settings and in that case, repo.Role role names which are passed to the ConnectionPool method or RenewPasswords will be suffixed automatically. Since the Schema repository has methods for creation of roles or asking to grant specific privileges to them, it needs to obtain the same role name suffix (as stored in the current Database instance).
The expected passwords hashing format of the target database must be configured in the `d.AuthMethod` field. Also, ValidateAndNormalize method is expected to be called beforehand, so it can create a hasher instance based on it. That hasher will be included in the returned Schema repo, so it may hash database role passwords properly.
func (Database) RenewPasswords ¶
func (d Database) RenewPasswords( ctx context.Context, change func( ctx context.Context, roles []repo.Role, passwords []string, ) error, roles ...repo.Role, ) (finalizer func() error, err error)
RenewPasswords generates new secure passwords for the given roles and after recording them in a temporary file (i.e., .pgpass.new file in the `d.PassDir` directory), will use the `change` function in order to update the passwords of those `roles` in the database too. The `change` function argument should perform the update operation in a transaction which may or may not be committed when the RenewPasswords function returns. In case of a successful commitment, the temporary passwords file should be moved over the main passwords file (i.e., .pgpass file in the `d.PassDir` directory). Keeping the .pgpass file up-to-date, makes it possible to use ConnectionPool method again (both if the passwords are or are not updated successfully). This final file movement can be performed using the returned finalizer function.
The `d.RoleSuffix` will be appended to the given role names too. The `change` function must add the same suffix to `roles` roles names in order to remain consistent with the in-file recorded information.
func (Database) SchemaMigrator ¶
func (d Database) SchemaMigrator(tx repo.Tx, srcDBVer model.SemVer) ( repo.Migrator[repo.SchemaSettler], error, )
SchemaMigrator creates a repo.Migrator[repo.SchemaSettler] instance which wraps the given `tx` transaction argument and can be used for
- loading the source database schema information with this assumption that tx belongs to the destination database and this `d` instance contains the Database connection information for the source database, so it can modify the destination database within a transaction and fill a schema with tables and views which represent the source database contents (not moving data items necessarily, but may create them as a foreign data wrapper, aka FDW too),
- creating upwards or downwards migrator objects in order to transform the loaded data into their upper/lower schema versions, again with minimal data transfer and using views instead of tables as far as possible, while creating tables or even loading data into this Golang process if it is necessary, and at last
- obtaining a repo.SchemaSettler instance for the target schema major version, so it can persist the target schema version by creating tables and filling them with contents of the corresponding views.
func (*Database) ValidateAndNormalize ¶
ValidateAndNormalize validates the database settings and returns an error if they were not acceptable. It can also modify settings in order to normalize them or replace some zero values with their expected default values (if any). So, it takes a pointer receiver instead of a non-reference receiver (in contrast to other methods).
type Gin ¶
type Gin struct { Logger *bool // Whether to register the gin.Logger() middleware Recovery *bool // Whether to register the gin.Recovery() middleware }
Gin contains the gin-gonic related configuration settings. Fields are defined as pointers, so it is possible to detect if they are or are not initialized. After migrating from some configuration settings version, some settings may be left uninitialized because they may have no corresponding items in the source settings version. Those items can be detected as nil pointers and filled by their default values using the MergeConfig method.
type Immutable ¶ added in v1.2.0
type Immutable struct { // Logger reports if server-side REST API logging is enabled. // // This field must always have a non-nil value when it represents // the setting value and must always be nil when it represents the // boundary values. Logger *bool `json:"logger"` }
Immutable contains settings which are immutable (and can be configured only using the configuration file or environment variables alone), but are visible by end-users (settings must be at least visible or mutable, otherwise, they may not be called a setting).
type Marshalled ¶
type Marshalled struct { Database Database Gin Gin Usecases struct { Cars struct { Delay *string `yaml:"old-parking-method-delay,omitempty"` MinDelay *string `yaml:"old-parking-method-delay-minimum,omitempty"` MaxDelay *string `yaml:"old-parking-method-delay-maximum,omitempty"` } } Vers *vers.Marshalled `yaml:",inline"` }
Marshalled struct contains a field for each one of the Config struct fields. The field names may be different for simplicity, but the yaml tag of fields are chosen to have consistent names after the serialization operation. The types of those fields are the same if their default serialization format is acceptable, otherwise, they will be serialized manually using the Marshal method and their target primitive types will be used in the Marshalled struct.
type OutOfBoundsSettingsError ¶ added in v1.3.0
type OutOfBoundsSettingsError struct { // Cars contains errors related to the cars use cases. Cars struct { // OldParkingDelay indicates the range violation error (if any) // with regards to the old parking method delay. OldParkingDelay *settings.OutOfRangeError[settings.Duration] } }
OutOfBoundsSettingsError has the same structure as the Serializable struct and its embedded structs with three differences:
- Fields are listed directly in OutOfBoundsSettingsError struct instead of being categorized based on their immutability and visibility status, because all of those settings may have an out of range error and all such errors should be reported together,
- Only that subset of fields is included which may observe a *settings.OutOfRangeError[T] error, because if other errors could happen, they would be reported by higher priority and they would obstruct the mutation request, while an OutOfBoundsSettingsError is returned by the Mutate method only when the caller is free to decide if error should be fatal or treated as a warning,
- The type of all included fields is *settings.OutOfRangeError[T] for different T types, where T is the actual type of that field from the Serializable struct.
func (*OutOfBoundsSettingsError) Error ¶ added in v1.3.0
func (e *OutOfBoundsSettingsError) Error() string
Error implements error interface and encodes whole of this OutOfBoundsSettingsError instance as an error string.
func (*OutOfBoundsSettingsError) IsBoundsError ¶ added in v1.3.0
func (e *OutOfBoundsSettingsError) IsBoundsError()
IsBoundsError implements the settings.BoundsError interface and so marks the *OutOfBoundsSettingsError as a boundary values violation error.
type Serializable ¶ added in v1.2.0
type Serializable struct { // Version indicates the format version of this Serializable and // is equal to the Config struct version. Although its value is // known from the Serializable type, but we have to store it as a // field in order to find it out during the deserialization and // application phase (by the Mutate method). // Therefore, the embedded Settings struct is enough at runtime. Version model.SemVer `json:"version"` Settings }
Serializable embeds the Settings in addition to a Version field, so it can be serialized and stored in the database, while the Version field may be consulted during its deserialization in order to ensure that it belongs to the same configuration format version. The Serializable and the main Config struct are versioned together. The nested Immutable pointer must be nil because the Serializable is supposed to carry the mutable settings which are acceptable to be queried from the database and may be passed to the Mutate method.
Serializable also can represent the minimum and maximum boundary values for settings. Because all mutable and immutable settings can have boundary values potentially, all fields may have a value in this use case. The version of the main settings and its boundary values (i.e., three instances of this struct) must be the same.
type Settings ¶ added in v1.2.0
type Settings struct {
Visible
}
Settings contains those settings which are mutable & invisible, that is, write-only settings. It also embeds the Visible struct so it effectively contains all kinds of settings. When fetching settings, the nested Immutable pointer can be set to nil in order to keep the mutable (visible or invisible) settings and when reporting settings, the embedded Visible struct can be reported alone (having a non-nil Immutable pointer) in order to exclude the invisible settings.
Some fields, such as Logger, were defined as a pointer in the Config struct because it was desired to detect if they were or were not initialized during a migration operation, so they could be filled by the MergeConfig method later. They had to obtain a value anyways after a call to the ValidateAndNormalize method and so nil is not a meaningful value for them. However, they must have a pointer type yet because Settings instances can represent the boundary values too. All fields which may need a minimum or maximum boundary value must be defined as a pointer so they can be left uninitialized when no such limit is desired. Using a non-pointer type means that a minimum and a maximum value must be provided for that setting (which is not possible for data types which are not ordered). Note that a nil value may not be used to communicate a scenario that user does not want to modify a setting. If a use case implementation wants to allow end-users to selectively configure settings, it is the responsibility of that implementation to replace such nil values with their old settings values and we can expect to set all fields of the Settings and Visible structs collectively. This is essential so caller can explicitly deinitialize a setting and enable its use case layer default value. If a use case requires to distinguish between deinitializing and not configuring a setting at all, an extra boolean setting may be sent such as `setting` and `setting_set` which its false value indicates that no setting is sent and its true value indicates that a value (which can be nil too) is sent. By the way, a use case which does not set some setting without having a UX meaning for it (such as a switch button for removing some restriction), increases the risk of conflicts because an end-user decides to selectively update one setting because they think that other settings have some seen values, but they have been changed concurrently. So it is preferred to ask the frontend to send the complete set of settings (whether they are set by end-user or their older seen values are left unchanged) in order to justify a PUT instead of a POST request method. Of course, that decision relies on the details of each use case and cannot be fixed in this layer.
Some fields, such as the old parking method delay, were defined as a pointer in the Config struct because they could be left uninitialized even after a call to the ValidateAndNormalize method. That is, nil is a meaningful value for them and asks the configuration instance not to pass their corresponding functional options to use cases. Those fields must have pointer types in the Settings and Visible structs, so they can be kept uninitialized even when stored in and read out from the database again. That is, even if a settings field has a non-nil value, but its corresponding field in the database has a nil value, it has to be overwritten by that nil because being uninitialized is a menaingful configuration decision which was taken and persisted in the database in that scenario.
Therefore, all fields must have pointer types, although some of them must be always non-nil (or they will poppulate an invalid value).
type Usecases ¶
type Usecases struct {
Cars Cars // cars use cases related settings
}
Usecases contains the configuration settings for all use cases.
type Visible ¶ added in v1.2.0
type Visible struct { // Cars represents the visible and mutable settings for the Cars // use cases. Cars struct { // OldParkingDelay indicates the old parking method delay. // // When used as a minimum or maximum boundary value, it will // indicate the smallest/largest acceptable delay, or no such // restriction if set to nil. OldParkingDelay *settings.Duration `json:"old_parking_delay"` } `json:"cars"` *Immutable }
Visible contains settings which are visible by end-users. These settings may be mutable or immutable. The immutable & visible settings are managed by the embedded Immutable struct. When it is desired to serialize and transmit settings to end-users, the Immutable pointer should be non-nil and its fields should be poppulated. However, when it is desired to fetch settings from end-users and deserialize them, the Immutable pointer should be set to nil in order to abandon them.