Programming from zero

Validate a configuration dictionary against required fields

mediumMini scripts — put it all together

Problem statement

This mini script checks configurations before they are used. It combines dictionary lookups, conditions and try/except.

A configuration must have three fields: host, port and env. It is invalid when:

  • A required field is missing. The check raises a "missing key" style error.
  • The port is out of range (not from 1 to 65535). The check raises a "bad value" style error.

The check itself only raises the error. The caller decides how to report it.

Common mistake: catching the wrong kind of error.

TEXT
Wrong:
try:
validate(config)
except ValueError:
print("invalid")

A missing field raises KeyError, which except ValueError does not catch, so the program crashes. Catch each kind you expect.

Your task: write validate(config) that raises for a missing field or a bad port. Check the three configs below and print config <n>: valid or config <n>: invalid (<reason>).

  • Config 1: host web-01, port 8080, env prod.
  • Config 2: host web-02, env prod (no port).
  • Config 3: host web-03, port 70000, env dev.

Expected output

TEXT
config 1: valid
config 2: invalid (missing field: port)
config 3: invalid (port out of range: 70000)

Approach

Loop over the required fields, then check the port. Raise a built-in exception for each problem and catch each one in the caller:

  • Python: raise KeyError(field) for a missing field and raise ValueError(...) for a bad port. In the handler, e.args[0] is the field name.
  • Java: there is no KeyError, so a missing field throws NoSuchElementException and a bad port throws IllegalArgumentException. e.getMessage() gives the text.
  • C++: a missing field throws std::out_of_range and a bad port throws std::invalid_argument. e.what() gives the text.

Port values are kept as text in every language, so the check converts them with int(...), Integer.parseInt(...) or std::stoi(...).

REQUIRED = ["host", "port", "env"]


def validate(config):
    for field in REQUIRED:
        if field not in config:
            raise KeyError(field)
    port = int(config["port"])
    if port < 1 or port > 65535:
        raise ValueError(f"port out of range: {port}")


configs = [
    {"env": "prod", "host": "web-01", "port": "8080"},
    {"env": "prod", "host": "web-02"},
    {"env": "dev", "host": "web-03", "port": "70000"},
]

n = 0
for config in configs:
    n += 1
    try:
        validate(config)
        print(f"config {n}: valid")
    except KeyError as e:
        print(f"config {n}: invalid (missing field: {e.args[0]})")
    except ValueError as e:
        print(f"config {n}: invalid ({e})")