Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ and versions are tracked in the repo-root `VERSION` file.
restores an existing model exactly or removes a partial new model.
- Rejected unreachable CLI command routes and ancestor/child option collisions,
including canonical-name, alias, option-name, and option-token conflicts.
- Rejected application declaration attributes outside each public API's
documented context instead of silently accepting and discarding them.
- Prevented list, CLI, and application call paths from creating or overwriting
caller-visible variables through undeclared internal scratch assignments.
- Eliminated an intermittent macOS Bash process-group race in supervised
Expand Down
6 changes: 6 additions & 0 deletions lib/bash/app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ unknown, or explicitly requested missing file is an error.

## Configuration

`base_app_init MODEL` accepts only `name=APP_KEY` and `description=TEXT`.
`base_app_config_define MODEL KEY TYPE` accepts `env=NAME`, `default=VALUE`,
`required=BOOL`, `secret=BOOL`, `enum=A,B`, `validator=FUNCTION`, and
`help=TEXT`. Attributes from one declaration context are rejected in the
other with usage status `2`.

```bash
base_app_init deploy name=deploy description="Example application"
base_app_config_define deploy channel enum \
Expand Down
28 changes: 22 additions & 6 deletions lib/bash/app/lib_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ __base_bash_libs_app_attr_allowed__() {
esac
}

__base_bash_libs_app_restrict_attrs__() {
local owner="$1" allowed="$2" key

for key in "${!__base_bash_libs_app_attrs[@]}"; do
__base_bash_libs_app_attr_allowed__ "$key" || return $?
case ",$allowed," in
*,"$key",*) ;;
*)
__base_bash_libs_app_error__ "$owner: attribute '$key' is not valid for this declaration."
return 2
;;
esac
done
}

__base_bash_libs_app_validate_bool__() {
[[ "${1-}" =~ ^(0|1|true|false|yes|no|on|off)$ ]]
}
Expand Down Expand Up @@ -248,6 +263,7 @@ __base_bash_libs_app_cleanup_dispatch__() {
}

# base_app_init - Initializes an optional application policy model.
# Usage: base_app_init MODEL [name=APP_KEY] [description=TEXT]
base_app_init() {
local model="${1-}" key

Expand All @@ -261,9 +277,7 @@ base_app_init() {
}
shift
__base_bash_libs_app_parse_attrs__ "$@" || return $?
for key in "${!__base_bash_libs_app_attrs[@]}"; do
__base_bash_libs_app_attr_allowed__ "$key" || return $?
done
__base_bash_libs_app_restrict_attrs__ base_app_init 'name,description' || return $?
if [[ -n "${__base_bash_libs_app_attrs[name]+set}" ]] &&
! __base_bash_libs_app_valid_key__ "${__base_bash_libs_app_attrs[name]}"; then
__base_bash_libs_app_error__ 'base_app_init: name must be a lowercase application key.'
Expand Down Expand Up @@ -304,6 +318,9 @@ base_app_init() {
}

# base_app_config_define - Defines one typed configuration value.
# Usage: base_app_config_define MODEL KEY TYPE [env=NAME] [default=VALUE]
# [required=BOOL] [secret=BOOL] [enum=A,B] [validator=FUNCTION]
# [help=TEXT]
base_app_config_define() {
local model="${1-}" key="${2-}" type="${3-}" argument config_key
local -a attrs=()
Expand Down Expand Up @@ -333,9 +350,8 @@ base_app_config_define() {
shift 3
for argument; do attrs+=("$argument"); done
__base_bash_libs_app_parse_attrs__ "${attrs[@]+${attrs[@]}}" || return $?
for argument in "${!__base_bash_libs_app_attrs[@]}"; do
__base_bash_libs_app_attr_allowed__ "$argument" || return $?
done
__base_bash_libs_app_restrict_attrs__ base_app_config_define \
'env,default,required,secret,enum,validator,help' || return $?
if [[ -n "${__base_bash_libs_app_attrs[env]+set}" ]] &&
! __base_bash_libs_app_valid_identifier__ "${__base_bash_libs_app_attrs[env]}"; then
__base_bash_libs_app_error__ "configuration '$key' has an invalid environment variable name."
Expand Down
44 changes: 44 additions & 0 deletions lib/bash/app/tests/lib_app.bats
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,50 @@ assert_demo_snapshot() {
[ "$source" = cli ]
}

@test "application declaration APIs accept only their documented attributes" {
base_app_init alpha name=alpha-app description="Alpha application"
[ "${__base_bash_libs_app_models[alpha\|name]}" = alpha-app ]
[ "${__base_bash_libs_app_models[alpha\|description]}" = "Alpha application" ]

base_app_config_define alpha mode string \
env=APP_TEST_MODE default=dev required=true secret=true \
enum=dev,prod validator=validate_test_label help="Execution mode"
[ "${__base_bash_libs_app_config[alpha\|mode\|env]}" = APP_TEST_MODE ]
[ "${__base_bash_libs_app_config[alpha\|mode\|default]}" = dev ]
[ "${__base_bash_libs_app_config[alpha\|mode\|required]}" = true ]
[ "${__base_bash_libs_app_config[alpha\|mode\|secret]}" = true ]
[ "${__base_bash_libs_app_config[alpha\|mode\|enum]}" = dev,prod ]
[ "${__base_bash_libs_app_config[alpha\|mode\|validator]}" = validate_test_label ]
[ "${__base_bash_libs_app_config[alpha\|mode\|help]}" = "Execution mode" ]
}

@test "cross-context application attributes fail before model mutation" {
base_app_init alpha name=original description="Original model"
base_app_config_define alpha existing string default=preserved
local before_keys="${__base_bash_libs_app_models[alpha\|config-keys]}"

if base_app_init alpha env=IGNORED; then
false
else
[ "$?" -eq 2 ]
fi
[ "${__base_bash_libs_app_models[alpha\|name]}" = original ]
[ "${__base_bash_libs_app_models[alpha\|description]}" = "Original model" ]
[ "${__base_bash_libs_app_config[alpha\|existing\|default]}" = preserved ]

if base_app_config_define alpha ignored string name=discarded; then
false
else
[ "$?" -eq 2 ]
fi
[ -z "${__base_bash_libs_app_config[alpha\|ignored\|type]+set}" ]
[ "${__base_bash_libs_app_models[alpha\|config-keys]}" = "$before_keys" ]

bats_run base_app_config_define alpha another string description=discarded
[ "$status" -eq 2 ]
[[ "$output" == *"attribute 'description' is not valid"* ]]
}

@test "missing required configuration and explicitly requested files fail clearly" {
declare_test_config
bats_run base_app_config_load demo
Expand Down
Loading