From e553a2c3384b33310fb85b818d2e78a9adbe1a76 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Wed, 26 Feb 2025 12:13:13 +0100 Subject: [PATCH] Add README and export gmconfig() type --- README.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ src/gmconfig.erl | 32 ++++++++++--------- 2 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..835f006 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# Gmconfig - A JSON-SCHEMA-based configuration management support library + +## Introduction + +This library offers the basic support functions for the Gajumaru configuration +management subsystem. It is based on JSON-Schema, and includes, among other things: + +* A reasonably complete JSON-Schema validator +* Validating user configurations against the schema +* In-service update of the user config +* Caching of the user config (and schema) as persistent terms +* Fast config lookups using key paths +* Lookups can handle both schema defaults and user-provided defaults + +## JSON-Schema validator + +The main thing the validator currently doesn't support is proper +management of complex sets of schemas and subSchemas. There are +some preparations made for supporting `"$id"` and uris, but this +is still incomplete. `"definitions"` and `"$ref"` properties are recognized. + +As almost anything is theoretically possible with JSON-Schema, +there are surely other things that are unsupported. + +### All standard data types + +* `"null"` +* `"boolean"` +* `"integer"` +* `"number"` +* `"boolean"` +* `"string"` +* `"array"` +* `"object"` + +### Dynamic properties + +* `"if"` +* `"not"` +* `"oneOf"` +* `"anyOf"` +* `"allOf"` + +### Static Properties + +#### Array + +* `"maxItems"` +* `"minItems"` +* `"uniqueItems"` +* `"prefixItems"` +* `"contains"` (`"minContains"`, `"maxContains"`) + +#### Object + +* `"properties"` (`"minProperties"`, `"maxProperties"`) +* `"patternProperties"` +* `"additionalProperties"` +* `"required"` + +#### String + +* `"pattern"` +* `"minLength"` +* `"maxLength"` + +#### Number, Integer + +* `"minimum"` +* `"maximum"` +* `"exclusiveMinimum"` +* `"exclusiveMinimum"` +* `"multipleOf"` (only if object is an integer) + +### `"$updateSemantics"` + +* `"replace"` (fully replaces any old data) +* `"merge"` (for objects, keeps and/or updates existing values) +* `"suggest"` (adds value if not already present) + diff --git a/src/gmconfig.erl b/src/gmconfig.erl index 25410b1..9bab755 100644 --- a/src/gmconfig.erl +++ b/src/gmconfig.erl @@ -10,11 +10,15 @@ -export([get_env/2, get_env/3]). --export([user_config/0]). --export([load_main_config_schema/0, % called from start phase 50 - load_system_defaults/0]). % called from start phase 60 --export([schema/0, schema/1, schema/2]). --export([load_schema/0]). +-export([user_config/0]). % returns current user config +-export([load_main_config_schema/0, + load_system_defaults/0]). +-export([load_user_config/0, + load_user_config/1]). +-export([schema/0, + schema/1, + schema/2]). +-export([load_schema/0]). % load_user_config() ensures schema is loaded -export([schema_default/1, schema_default/2]). -export([schema_keys/0, schema_keys/1]). -export([config_value/4]). @@ -23,7 +27,6 @@ ]). -export([merge_config_maps/2]). -export([nested_map_get/2]). --export([read_config/0, read_config/1]). -export([load_config_file/1]). % apply system-/network-specific defaults -export([search_for_config_file/2]). -export([apply_os_env/0, @@ -75,7 +78,8 @@ -type find_opts() :: [user_config | schema_default | {env, atom(), atom()} | {value, any()}]. --export_type([ find_opts/0 ]). +-export_type([ gmconfig/0 + , find_opts/0 ]). -ifdef(TEST). @@ -475,17 +479,17 @@ schema_default_(#{default := Def}) -> schema_default_(_) -> undefined. -read_config() -> - read_config(silent). +load_user_config() -> + load_user_config(silent). -read_config(Mode) when Mode =:= check; Mode =:= silent; Mode =:= report -> +load_user_config(Mode) when Mode =:= check; Mode =:= silent; Mode =:= report -> case config_file() of undefined -> info_msg(Mode, "No config file specified; using default settings~n", []), ok; F -> info_msg(Mode, "Reading config file ~s~n", [F]), - do_read_config(F, store, Mode) + do_load_user_config(F, store, Mode) end. load_config_file(File) -> @@ -494,7 +498,7 @@ load_config_file(File) -> load_config_file(File, Mode) when Mode =:= check; Mode =:= silent; Mode =:= report -> - do_read_config(File, store, Mode). + do_load_user_config(File, store, Mode). apply_os_env() -> ok = application:ensure_started(gproc), @@ -608,7 +612,7 @@ unhyphenate(Str) -> re:replace(Str, <<"\\-">>, <<"_">>, [global, {return, list}]). check_config(F) -> - do_read_config(F, check, check). + do_load_user_config(F, check, check). data_dir(Name) when is_atom(Name) -> filename:join([setup:data_dir(), Name]). @@ -665,7 +669,7 @@ to_list_string(S) when is_binary(S) -> to_list_string(S) when is_list(S) -> binary_to_list(iolist_to_binary(S)). -do_read_config(F, Action, Mode) -> +do_load_user_config(F, Action, Mode) -> case {filename:extension(F), Action} of {".json", store} -> store(read_json(F, Mode), Mode); {".yaml", store} -> store(read_yaml(F, Mode), Mode);