res_geolocation: Allow location parameters on the profile object

You can now specify the location object's format, location_info,
method, location_source and confidence parameters directly on
a profile object for simple scenarios where the location
information isn't common with any other profiles.  This is
mutually exclusive with setting location_reference on the
profile.

Updated appdocsxml.dtd to allow xi:include in a configObject
element.  This makes it easier to link to complete configOptions
in another object.  This is used to add the above fields to the
profile object without having to maintain the option descriptions
in two places.

ASTERISK-30185

Change-Id: Ifd5f05be0a76f0a6ad49fa28d17c394027677569
This commit is contained in:
George Joseph 2022-08-18 06:29:09 -06:00 committed by Friendly Automation
parent 81ede203b6
commit b221f0f86a
8 changed files with 162 additions and 81 deletions

View File

@ -285,6 +285,13 @@ would have an empty <FLR/> element in it. If suppress_empty_ca_elements
were set to "yes", the FLR element would be dropped from the PIDF-LO
document altogether.
-- format, location_info, location_source, method, confidence ---------
You can specify the location object's format, location_info,
method, location_source and confidence parameters directly on
a profile object for simple scenarios where the location
information isn't common with any other profiles. This is
mutually exclusive with setting location_reference on the
profile.
-- Profile Example ----------------------------------------------------

View File

@ -33,3 +33,10 @@ set.
Added profile parameter "suppress_empty_ca_elements" that
will cause Civic Address elements that are empty to be
suppressed from the outgoing PIDF-LO document.
You can now specify the location object's format, location_info,
method, location_source and confidence parameters directly on
a profile object for simple scenarios where the location
information isn't common with any other profiles. This is
mutually exclusive with setting location_reference on the
profile.

View File

@ -69,10 +69,10 @@
<!ATTLIST configInfo name CDATA #REQUIRED>
<!ATTLIST configInfo language CDATA #REQUIRED>
<!ELEMENT configFile (configObject+)>
<!ELEMENT configFile (configObject|xi:include)+>
<!ATTLIST configFile name CDATA #REQUIRED>
<!ELEMENT configObject (synopsis?|description?|syntax?|see-also?|configOption)*>
<!ELEMENT configObject (synopsis?|description?|syntax?|see-also?|(configOption|xi:include))*>
<!ATTLIST configObject name CDATA #REQUIRED>
<!ELEMENT configOption (synopsis,description?,syntax?,see-also?)*>

View File

@ -75,6 +75,8 @@ struct ast_geoloc_profile {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(location_reference);
AST_STRING_FIELD(notes);
AST_STRING_FIELD(method);
AST_STRING_FIELD(location_source);
);
enum ast_geoloc_pidf_element pidf_element;
enum ast_geoloc_precedence precedence;
@ -83,6 +85,9 @@ struct ast_geoloc_profile {
struct ast_variable *location_variables;
struct ast_variable *usage_rules;
int suppress_empty_ca_elements;
enum ast_geoloc_format format;
struct ast_variable *location_info;
struct ast_variable *confidence;
};
struct ast_geoloc_eprofile {

View File

@ -67,13 +67,17 @@ static void *geoloc_location_alloc(const char *name)
return location;
}
CONFIG_ENUM(profile, pidf_element)
CONFIG_ENUM(profile, precedence)
CONFIG_VAR_LIST(profile, location_refinement)
CONFIG_VAR_LIST(profile, location_variables)
CONFIG_VAR_LIST(profile, usage_rules)
CONFIG_ENUM_HANDLER(profile, format)
CONFIG_ENUM_TO_STR(profile, format)
CONFIG_VAR_LIST(profile, location_info)
CONFIG_VAR_LIST(profile, confidence)
static void geoloc_profile_destructor(void *obj) {
struct ast_geoloc_profile *profile = obj;
@ -81,6 +85,8 @@ static void geoloc_profile_destructor(void *obj) {
ast_variables_destroy(profile->location_refinement);
ast_variables_destroy(profile->location_variables);
ast_variables_destroy(profile->usage_rules);
ast_variables_destroy(profile->location_info);
ast_variables_destroy(profile->confidence);
}
static void *geoloc_profile_alloc(const char *name)
@ -94,60 +100,83 @@ static void *geoloc_profile_alloc(const char *name)
return profile;
}
static int geoloc_location_apply_handler(const struct ast_sorcery *sorcery, void *obj)
static enum ast_geoloc_validate_result validate_location_info(const char *id,
enum ast_geoloc_format format, struct ast_variable *location_info)
{
struct ast_geoloc_location *location = obj;
const char *location_id = ast_sorcery_object_get_id(location);
enum ast_geoloc_validate_result result;
const char *failed;
const char *uri;
enum ast_geoloc_validate_result result;
switch (location->format) {
switch (format) {
case AST_GEOLOC_FORMAT_NONE:
case AST_GEOLOC_FORMAT_LAST:
ast_log(LOG_ERROR, "Location '%s' must have a format\n", location_id);
ast_log(LOG_ERROR, "Location '%s' must have a format\n", id);
return -1;
case AST_GEOLOC_FORMAT_CIVIC_ADDRESS:
result = ast_geoloc_civicaddr_validate_varlist(location->location_info, &failed);
result = ast_geoloc_civicaddr_validate_varlist(location_info, &failed);
if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
ast_log(LOG_ERROR, "Location '%s' has invalid item '%s' in the location\n",
location_id, failed);
return -1;
id, failed);
return result;
}
break;
case AST_GEOLOC_FORMAT_GML:
result = ast_geoloc_gml_validate_varlist(location->location_info, &failed);
result = ast_geoloc_gml_validate_varlist(location_info, &failed);
if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
ast_log(LOG_ERROR, "%s for item '%s' in location '%s'\n",
ast_geoloc_validate_result_to_str(result), failed, location_id);
return -1;
ast_geoloc_validate_result_to_str(result), failed, id);
return result;
}
break;
case AST_GEOLOC_FORMAT_URI:
uri = ast_variable_find_in_list(location->location_info, "URI");
uri = ast_variable_find_in_list(location_info, "URI");
if (!uri) {
struct ast_str *str = ast_variable_list_join(location->location_info, ",", "=", "\"", NULL);
struct ast_str *str = ast_variable_list_join(location_info, ",", "=", "\"", NULL);
ast_log(LOG_ERROR, "Geolocation location '%s' format is set to '%s' but no 'URI' was found in location parameter '%s'\n",
location_id, format_names[AST_GEOLOC_FORMAT_URI], ast_str_buffer(str));
id, format_names[AST_GEOLOC_FORMAT_URI], ast_str_buffer(str));
ast_free(str);
return -1;
return AST_GEOLOC_VALIDATE_NOT_ENOUGH_VARNAMES;
}
break;
}
if (!ast_strlen_zero(location->location_source)) {
return AST_GEOLOC_VALIDATE_SUCCESS;
}
static int validate_location_source(const char *id, const char *location_source)
{
if (!ast_strlen_zero(location_source)) {
struct ast_sockaddr loc_source_addr;
int rc = ast_sockaddr_parse(&loc_source_addr, location->location_source, PARSE_PORT_FORBID);
int rc = ast_sockaddr_parse(&loc_source_addr, location_source, PARSE_PORT_FORBID);
if (rc == 1) {
ast_log(LOG_ERROR, "Geolocation location '%s' location_source '%s' must be a FQDN."
" RFC8787 expressly forbids IP addresses.\n",
location_id, location->location_source);
id, location_source);
return -1;
}
}
return 0;
}
static int geoloc_location_apply_handler(const struct ast_sorcery *sorcery, void *obj)
{
struct ast_geoloc_location *location = obj;
const char *location_id = ast_sorcery_object_get_id(location);
enum ast_geoloc_validate_result result;
int rc = 0;
result = validate_location_info(location_id, location->format, location->location_info);
if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
return -1;
}
rc = validate_location_source(location_id, location->location_source);
if (rc != 0) {
return -1;
}
return 0;
}
@ -156,48 +185,52 @@ static int geoloc_profile_apply_handler(const struct ast_sorcery *sorcery, void
{
struct ast_geoloc_profile *profile = obj;
struct ast_geoloc_location *location;
const char *profile_id = ast_sorcery_object_get_id(profile);
const char *failed;
const char *id = ast_sorcery_object_get_id(profile);
enum ast_geoloc_validate_result result;
enum ast_geoloc_format format;
int rc = 0;
if (ast_strlen_zero(profile->location_reference)) {
if (profile->location_refinement ||
profile->location_variables) {
ast_log(LOG_ERROR, "Profile '%s' can't have location_refinement or location_variables without a location_reference",
profile_id);
if (!ast_strlen_zero(profile->location_reference)) {
if (profile->location_info ||
profile->format != AST_GEOLOC_FORMAT_NONE) {
ast_log(LOG_ERROR, "Profile '%s' can't have location_reference and location_info or format at the same time",
id);
return -1;
}
return 0;
}
location = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", profile->location_reference);
if (!location) {
ast_log(LOG_ERROR, "Profile '%s' has a location_reference '%s' that doesn't exist",
profile_id, profile->location_reference);
return -1;
if (profile->location_info) {
result = validate_location_info(id, profile->format, profile->location_info);
if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
return -1;
}
rc = validate_location_source(id, profile->location_source);
if (rc != 0) {
return -1;
}
return 0;
}
if (!ast_strlen_zero(profile->location_reference)) {
location = ast_sorcery_retrieve_by_id(geoloc_sorcery, "location", profile->location_reference);
if (!location) {
ast_log(LOG_ERROR, "Profile '%s' has a location_reference '%s' that doesn't exist",
id, profile->location_reference);
return -1;
}
format = location->format;
ao2_ref(location, -1);
}
if (profile->location_refinement) {
switch (location->format) {
case AST_GEOLOC_FORMAT_NONE:
case AST_GEOLOC_FORMAT_LAST:
break;
case AST_GEOLOC_FORMAT_CIVIC_ADDRESS:
result = ast_geoloc_civicaddr_validate_varlist(profile->location_refinement, &failed);
if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
ast_log(LOG_ERROR, "Profile '%s' error: %s: for item '%s' in the location_refinement\n",
profile_id, ast_geoloc_validate_result_to_str(result), failed);
ao2_ref(location, -1);
return -1;
}
break;
case AST_GEOLOC_FORMAT_GML:
break;
case AST_GEOLOC_FORMAT_URI:
break;
result = validate_location_info(id, format, profile->location_refinement);
if (result != AST_GEOLOC_VALIDATE_SUCCESS) {
return -1;
}
}
ao2_ref(location, -1);
return 0;
}
@ -217,7 +250,6 @@ static char *geoloc_config_list_locations(struct ast_cli_entry *e, int cmd, stru
int using_regex = 0;
char *result = CLI_SUCCESS;
int ret = 0;
char *format_name;
int count = 0;
switch (cmd) {
@ -285,14 +317,12 @@ static char *geoloc_config_list_locations(struct ast_cli_entry *e, int cmd, stru
break;
}
format_to_str(loc, NULL, &format_name);
ast_cli(a->fd, "%-46.46s %-13s %-s\n",
ast_sorcery_object_get_id(loc),
format_name,
format_names[loc->format],
ast_str_buffer(str));
ao2_unlock(loc);
ast_free(str);
ast_free(format_name);
count++;
}
ao2_iterator_destroy(&iter);
@ -311,7 +341,6 @@ static char *geoloc_config_list_profiles(struct ast_cli_entry *e, int cmd, struc
int using_regex = 0;
char *result = CLI_SUCCESS;
int ret = 0;
char *precedence;
int count = 0;
switch (cmd) {
@ -368,13 +397,11 @@ static char *geoloc_config_list_profiles(struct ast_cli_entry *e, int cmd, struc
for (; (profile = ao2_iterator_next(&iter)); ao2_ref(profile, -1)) {
ao2_lock(profile);
precedence_to_str(profile, NULL, &precedence);
ast_cli(a->fd, "%-46.46s %-16s %-s\n",
ast_sorcery_object_get_id(profile),
precedence,
precedence_names[profile->precedence],
profile->location_reference);
ao2_unlock(profile);
ast_free(precedence);
count++;
}
ao2_iterator_destroy(&iter);
@ -441,7 +468,7 @@ static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struc
return CLI_FAILURE;
}
ast_cli(a->fd, "Geolocation Profile Objects:\n\n");
ast_cli(a->fd, "Geolocation Profile Objects:\n");
iter = ao2_iterator_init(sorted_container, AO2_ITERATOR_UNLINK);
for (; (profile = ao2_iterator_next(&iter)); ) {
@ -450,26 +477,28 @@ static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struc
struct ast_str *variables_str = NULL;
struct ast_str *resolved_str = NULL;
struct ast_str *usage_rules_str = NULL;
struct ast_str *confidence_str = NULL;
struct ast_geoloc_eprofile *eprofile = ast_geoloc_eprofile_create_from_profile(profile);
ao2_ref(profile, -1);
if (!ast_strlen_zero(eprofile->location_reference)) {
loc_str = ast_variable_list_join(eprofile->location_info, ",", "=", "\"", NULL);
resolved_str = ast_variable_list_join(eprofile->effective_location, ",", "=", "\"", NULL);
}
loc_str = ast_variable_list_join(eprofile->location_info, ",", "=", "\"", NULL);
resolved_str = ast_variable_list_join(eprofile->effective_location, ",", "=", "\"", NULL);
refinement_str = ast_variable_list_join(eprofile->location_refinement, ",", "=", "\"", NULL);
variables_str = ast_variable_list_join(eprofile->location_variables, ",", "=", "\"", NULL);
usage_rules_str = ast_variable_list_join(eprofile->usage_rules, ",", "=", "\"", NULL);
confidence_str = ast_variable_list_join(eprofile->confidence, ",", "=", "\"", NULL);
ast_cli(a->fd,
ast_cli(a->fd,"\n"
"id: %-s\n"
"profile_precedence: %-s\n"
"pidf_element: %-s\n"
"location_reference: %-s\n"
"Location_format: %-s\n"
"location_details: %-s\n"
"location_format: %-s\n"
"location_info: %-s\n"
"location_method: %-s\n"
"location_source: %-s\n"
"location_confidence: %-s\n"
"location_refinement: %-s\n"
"location_variables: %-s\n"
"allow_routing_use: %-s\n"
@ -484,6 +513,8 @@ static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struc
format_names[eprofile->format],
S_COR(loc_str, ast_str_buffer(loc_str), "<none>"),
S_OR(eprofile->method, "<none>"),
S_OR(eprofile->location_source, "<none>"),
S_COR(confidence_str, ast_str_buffer(confidence_str), "<none>"),
S_COR(refinement_str, ast_str_buffer(refinement_str), "<none>"),
S_COR(variables_str, ast_str_buffer(variables_str), "<none>"),
S_COR(eprofile->allow_routing_use, "yes", "no"),
@ -499,6 +530,7 @@ static char *geoloc_config_show_profiles(struct ast_cli_entry *e, int cmd, struc
ast_free(variables_str);
ast_free(resolved_str);
ast_free(usage_rules_str);
ast_free(confidence_str);
count++;
}
ao2_iterator_destroy(&iter);
@ -643,11 +675,11 @@ int geoloc_config_load(void)
ast_sorcery_object_field_register(geoloc_sorcery, "location", "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "format", AST_GEOLOC_FORMAT_NONE,
format_handler, format_to_str, NULL, 0, 0);
location_format_handler, location_format_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "location_info", NULL,
location_info_handler, location_info_to_str, location_info_dup, 0, 0);
location_location_info_handler, location_location_info_to_str, location_location_info_dup, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "location", "confidence", NULL,
confidence_handler, confidence_to_str, confidence_dup, 0, 0);
location_confidence_handler, location_confidence_to_str, location_confidence_dup, 0, 0);
ast_sorcery_object_field_register(geoloc_sorcery, "location", "location_source", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_location, location_source));
ast_sorcery_object_field_register(geoloc_sorcery, "location", "method", "", OPT_STRINGFIELD_T,
@ -678,17 +710,17 @@ int geoloc_config_load(void)
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "pidf_element",
pidf_element_names[AST_PIDF_ELEMENT_DEVICE], pidf_element_handler, pidf_element_to_str, NULL, 0, 0);
pidf_element_names[AST_PIDF_ELEMENT_DEVICE], profile_pidf_element_handler, profile_pidf_element_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "location_reference", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_profile, location_reference));
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "profile_precedence", "discard_incoming",
precedence_handler, precedence_to_str, NULL, 0, 0);
profile_precedence_handler, profile_precedence_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "usage_rules", NULL,
usage_rules_handler, usage_rules_to_str, usage_rules_dup, 0, 0);
profile_usage_rules_handler, profile_usage_rules_to_str, profile_usage_rules_dup, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_info_refinement", NULL,
location_refinement_handler, location_refinement_to_str, location_refinement_dup, 0, 0);
profile_location_refinement_handler, profile_location_refinement_to_str, profile_location_refinement_dup, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_variables", NULL,
location_variables_handler, location_variables_to_str, location_variables_dup, 0, 0);
profile_location_variables_handler, profile_location_variables_to_str, profile_location_variables_dup, 0, 0);
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "notes", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_profile, notes));
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "allow_routing_use",
@ -696,6 +728,17 @@ int geoloc_config_load(void)
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "suppress_empty_ca_elements",
"no", OPT_BOOL_T, 1, FLDSET(struct ast_geoloc_profile, suppress_empty_ca_elements));
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "format", AST_GEOLOC_FORMAT_NONE,
profile_format_handler, profile_format_to_str, NULL, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "location_info", NULL,
profile_location_info_handler, profile_location_info_to_str, profile_location_info_dup, 0, 0);
ast_sorcery_object_field_register_custom(geoloc_sorcery, "profile", "confidence", NULL,
profile_confidence_handler, profile_confidence_to_str, profile_confidence_dup, 0, 0);
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "location_source", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_profile, location_source));
ast_sorcery_object_field_register(geoloc_sorcery, "profile", "method", "", OPT_STRINGFIELD_T,
0, STRFLDSET(struct ast_geoloc_profile, method));
ast_sorcery_load(geoloc_sorcery);

View File

@ -207,6 +207,11 @@
</enumlist>
</description>
</configOption>
<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='format'])"/>
<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='location_info'])"/>
<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='confidence'])"/>
<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='location_source'])"/>
<xi:include xpointer="xpointer(/docs/configInfo[@name='res_geolocation']/configFile[@name='geolocation.conf']/configObject[@name='location']/configOption[@name='method'])"/>
</configObject>
</configFile>
</configInfo>

View File

@ -177,11 +177,22 @@ struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_g
eprofile->allow_routing_use = profile->allow_routing_use;
eprofile->pidf_element = profile->pidf_element;
eprofile->suppress_empty_ca_elements = profile->suppress_empty_ca_elements;
eprofile->format = profile->format;
rc = ast_string_field_set(eprofile, location_reference, profile->location_reference);
if (rc == 0) {
ast_string_field_set(eprofile, notes, profile->notes);
}
if (rc == 0) {
ast_string_field_set(eprofile, method, profile->method);
}
if (rc == 0) {
ast_string_field_set(eprofile, location_source, profile->location_source);
}
if (rc == 0) {
rc = DUP_VARS(eprofile->location_info, profile->location_info);
}
if (rc == 0) {
rc = DUP_VARS(eprofile->location_refinement, profile->location_refinement);
}
@ -191,6 +202,9 @@ struct ast_geoloc_eprofile *ast_geoloc_eprofile_create_from_profile(struct ast_g
if (rc == 0) {
rc = DUP_VARS(eprofile->usage_rules, profile->usage_rules);
}
if (rc == 0) {
rc = DUP_VARS(eprofile->confidence, profile->confidence);
}
if (rc != 0) {
ao2_unlock(profile);
ao2_ref(eprofile, -1);

View File

@ -38,7 +38,7 @@ int ast_geoloc_ ## _stem ## _str_to_enum(const char *str) \
}
#define CONFIG_ENUM_HANDLER(_object, _stem) \
static int _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
static int _object ## _ ## _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
{ \
struct ast_geoloc_ ## _object *_thisobject = obj; \
int enumval = ast_geoloc_ ## _stem ## _str_to_enum(var->value); \
@ -61,7 +61,7 @@ const char * ast_geoloc_ ## _stem ## _to_name(int ix) \
}
#define CONFIG_ENUM_TO_STR(_object, _stem) \
static int _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
static int _object ## _ ## _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
{ \
const struct ast_geoloc_ ## _object *_thisobject = obj; \
if (!ARRAY_IN_BOUNDS(_thisobject->_stem, _stem ## _names)) { \
@ -79,7 +79,7 @@ CONFIG_ENUM_HANDLER(_object, _stem) \
CONFIG_ENUM_TO_STR(_object, _stem)
#define CONFIG_VAR_LIST_HANDLER(_object, _stem) \
static int _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
static int _object ## _ ## _stem ## _handler(const struct aco_option *opt, struct ast_variable *var, void *obj) \
{ \
struct ast_geoloc_ ## _object *_thisobject = obj; \
struct ast_variable *new_var; \
@ -101,7 +101,7 @@ static int _stem ## _handler(const struct aco_option *opt, struct ast_variable *
}
#define CONFIG_VAR_LIST_DUP(_object, _stem) \
static int _stem ## _dup(const void *obj, struct ast_variable **fields) \
static int _object ## _ ## _stem ## _dup(const void *obj, struct ast_variable **fields) \
{ \
const struct ast_geoloc_ ## _object *_thisobject = obj; \
if (_thisobject->_stem) { \
@ -111,7 +111,7 @@ static int _stem ## _dup(const void *obj, struct ast_variable **fields) \
}
#define CONFIG_VAR_LIST_TO_STR(_object, _stem) \
static int _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
static int _object ## _ ## _stem ## _to_str(const void *obj, const intptr_t *args, char **buf) \
{ \
const struct ast_geoloc_ ## _object *_thisobject = obj; \
struct ast_str *str = ast_variable_list_join(_thisobject->_stem, ",", "=", "\"", NULL); \