From 9bc22f8b3c13e06cc43a20f9be0d5b1ef394b744 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Thu, 30 Oct 2025 19:19:23 -0700 Subject: [PATCH] Better glom path --- backend/bdd/features/pki/acme/auth.feature | 3 +- backend/bdd/features/steps/pki_acme.py | 57 +++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/backend/bdd/features/pki/acme/auth.feature b/backend/bdd/features/pki/acme/auth.feature index 401891b6e..6a542ea63 100644 --- a/backend/bdd/features/pki/acme/auth.feature +++ b/backend/bdd/features/pki/acme/auth.feature @@ -16,4 +16,5 @@ Feature: Order Then I create a RSA private key pair as cert_key Then I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format Then I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order - Then the value order.authorizations[0].uri with jq . should match pattern {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/authorizations/(.+) + Then the value order.authorizations[0] with jq .uri should match pattern {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/authorizations/(.+) + Then the value order.authorizations[0] with jq . should be equal to {} diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py index 2df1adfa0..c5f8e068b 100644 --- a/backend/bdd/features/steps/pki_acme.py +++ b/backend/bdd/features/steps/pki_acme.py @@ -3,13 +3,13 @@ import re import jq import requests +import glom from acme import client from acme import messages from behave.runner import Context from behave import given from behave import when from behave import then -import glom from josepy.jwk import JWKRSA from josepy import JSONObjectWithFields from cryptography.hazmat.primitives import serialization @@ -41,11 +41,64 @@ def replace_vars(payload: dict | list | int | float | str, vars: dict): return payload +def parse_glom_path(path_str: str): + """ + Parse a glom path string with 'attr[index]' syntax into a Path object. + + Examples: + >>> parse_glom_path('authorizations[0].name') + Path('authorizations', 0, 'name') + + >>> parse_glom_path('items[1].user[0].id') + Path('items', 1, 'user', 0, 'id') + + >>> parse_glom_path('simple_attr') + Path('simple_attr') + + >>> parse_glom_path('nested.attr') + Path('nested', 'attr') + """ + # Pattern to match attr[index] or just attr + # Groups: (attr_name)(?:\[(index)\])? + pattern = r"([a-zA-Z_][a-zA-Z0-9_]*)(?:\[(\d+)\])?" + + # Split on dots, but preserve the parts + parts = [] + current_pos = 0 + + # Find all matches in the string + for match in re.finditer(pattern, path_str): + # Add any literal text before this match + if match.start() > current_pos: + before = path_str[current_pos : match.start()] + raise ValueError(f"Invalid path syntax: unexpected text '{before}'") + + attr_name = match.group(1) + index = match.group(2) + + # Add the attribute name + parts.append(attr_name) + + # Add index if present + if index is not None: + parts.append(int(index)) + + current_pos = match.end() + + # Check for trailing text + if current_pos < len(path_str): + after = path_str[current_pos:] + if after != ".": + raise ValueError(f"Invalid path syntax: unexpected text '{after}'") + + return glom.Path(*parts) + + def eval_var(context: Context, var_path: str, as_json: bool = True): parts = var_path.split(".", 1) value = context.vars[parts[0]] if len(parts) == 2: - value = glom.glom(value, parts[1]) + value = glom.glom(value, parse_glom_path(parts[1])) if as_json: if isinstance(value, JSONObjectWithFields): value = value.to_json()