Skip to content
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,33 @@ A name like `root.a` will be translated to
a search for an environment variable named
`ROOT__A`.

Read the [API](https://hummus.dpldocs.info/hummus.providers.env.html).

### `JSONProvider`

A provider which will look for JSON key-value pairs
based on matching them to the names of the fields in
the provided struct.

Struct fields which are of a struct-type themselves
are supported and are filled whenever json such as
`x.y` is encountered. This means `x` is some field
in the "outer" struct. Then because we have `x.y`,
`x` MUST be of a struct type. Then we access the field
named `y` in this "inner" struct.

Hence a name like `root.a` will be translated to
an access at `jsonObject["root"].a` (where `jsonObject`
is the root document).

Read the [API](https://hummus.dpldocs.info/hummus.providers.json.html).

# Development

## Testing

In order to run the full test suite use the following
command:
command (or just `./test.sh`):

```d
V=1 I__Z=2 dub test
Expand All @@ -97,6 +118,13 @@ is for the `EnvironmentProvider` which searches
for environment variables, and in particular its
unittest looks for those two.

### Build configurations

1. `debug`
* In this mode the `gogga` logging library is
brought in and various flags are set to enable
verbose logging that aids debugging

## License

Licensed under the LGPL-2.0-only .
12 changes: 6 additions & 6 deletions dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
],
"copyright": "Copyright © 2025, Tristan Brice Velloza Kildaire",
"dependencies": {
"niknaks": ">=0.26.2"
"niknaks": ">=0.27.0"
},
"description": "Pluggable configuration engine",
"license": "LGPL-2.0-only",
"name": "hummus",
"targetType": "library",
"configurations": {
"debug": {
"dependencies": {
"gogga": ">=3.2.0"
},
"versions": ["DBG_DEBUG_LOGGING", "DBG_VERBOSE_LOGGING"]
"dependencies": {
"gogga": ">=3.2.0"
},
"versions": ["DBG_DEBUG_LOGGING", "DBG_VERBOSE_LOGGING"]
}
}
}
}
167 changes: 167 additions & 0 deletions source/hummus/providers/json.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* JSON-based provider
*
* You should use this when
* you want to fill up your
* config with values stored
* in a string containing
* JSON-encoded data
*
* Authors: Tristan Brice Velloza Kildaire (deavmi)
*/
module hummus.providers.json;

import hummus.provider : Provider;
import std.json : JSONValue, JSONType;
import niknaks.json : traverseTo;

version(unittest)
{
import gogga.mixins;
}

/**
* A provider which will look for
* JSON key-value pairs based on
* matching them to the names of the
* fields in the provided struct.
*
* Struct fields which are of
* a struct-type themselves are
* supported and are filled
* whenever json such as `x.y`
* is encountered. This means `x`
* is some field in the "outer"
* struct. Then because we have
* `x.y`, `x` MUST be of a struct
* type. Then we access the field
* named `y` in this "inner" struct
*/
public class JSONProvider : Provider
{
import std.json : parseJSON, JSONException;

private JSONValue _j;

/**
* Constructs a new JSON provider
* with the given input JSON to
* parse
*
* Params:
* json = the input JSON
* Throws:
* JSONException when parsing
* fails
*/
this(string json)
{
// todo: handle exceptions in non-library specific way
// OR require they parse in the JSONValue - that is
// library-dependent tho
this._j = parseJSON(json);
}

/**
* Implementation
*/
protected bool provideImpl(string n, ref string v)
{
// todo: check return value for nullity
JSONValue* f_node = traverseTo(n, &this._j);

// todo: value conversion here
if(f_node is null)
{
return false;
}

string s_out;
if(jsonNormal(f_node, s_out))
{
version(unittest)
DEBUG("found JSON node toString(): ", s_out);

v = s_out;
return true;
}
else
{
return false;
}
}
}

private bool jsonNormal(JSONValue* i, ref string o)
{
auto t = i.type();
if(t == JSONType.string)
{
o = i.str();
return true;
}
else if(t == JSONType.ARRAY)
{
version(unittest)
DEBUG("'", i, "' is an array type, these are unsupported");

return false;
}
// todo: disallow array types and object types
else
{
o = i.toString();
return true;
}
}

private version(unittest)
{
import hummus.cfg : fieldsOf;
import std.stdio : writeln;
}

unittest
{
struct Inner
{
int prop;
int k;
}

struct Basic
{
string name;
ulong age;
Inner x;
string bad;
}

auto cfg = Basic();
writeln("Before provisioning: ", cfg);

// input json
string json = `
{
"name": "Tristan Brice Velloza Kildaire",
"age": 25,
"x": {
"prop": 2
},
"bad": ["", 2]
}
`;

// create a new JSON provider with the
// input JSON
fieldsOf(cfg, new JSONProvider(json));

assert(cfg.name == "Tristan Brice Velloza Kildaire");
assert(cfg.age == 25);
assert(cfg.x.prop == 2);

// it is not present (in the JSON) hence it should
// never be set in our struct
assert(cfg.x.k == cfg.x.k.init);
assert(cfg.bad.length == 0);
}
2 changes: 2 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
V=1 I__Z=2 dub test