Skip to content

Commit

Permalink
New error message translation framework
Browse files Browse the repository at this point in the history
  • Loading branch information
terryburton committed Jan 30, 2025
1 parent 5ec354e commit 0bd7b04
Show file tree
Hide file tree
Showing 14 changed files with 429 additions and 129 deletions.
44 changes: 23 additions & 21 deletions src/c-lib/ai.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* GS1 Barcode Syntax Engine
*
* @author Copyright (c) 2021-2024 GS1 AISBL.
* @author Copyright (c) 2021-2025 GS1 AISBL.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,6 +31,7 @@
#include "debug.h"
#include "ai.h"
#include "dl.h"
#include "tr.h"


/*
Expand Down Expand Up @@ -92,7 +93,7 @@ static bool populateAIlengthByPrefix(gs1_encoder* const ctx) {
uint8_t prefix = (uint8_t)((e->ai[0] - '0') * 10 + (e->ai[1] - '0'));
uint8_t length = (uint8_t)strlen(e->ai);
if (ctx->aiLengthByPrefix[prefix] != 0 && ctx->aiLengthByPrefix[prefix] != length) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI table is broken: AIs beginning '%c%c' have different lengths", e->ai[0], e->ai[1]);
SET_ERR_V(AI_TABLE_BROKEN_PREFIXES_DIFFER_IN_LENGTH, e->ai[0], e->ai[1]);
return false;
}
ctx->aiLengthByPrefix[prefix] = length;
Expand Down Expand Up @@ -337,7 +338,7 @@ static size_t validate_ai_val(gs1_encoder* const ctx, const char* const ai, cons
DEBUG_PRINT(" Considering AI (%.*s): %.*s\n", (int)strlen(entry->ai), ai, (int)(r-p), start);

if (p == r) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) data is empty", (int)strlen(entry->ai), ai);
SET_ERR_V(AI_DATA_IS_EMPTY, (int)strlen(entry->ai), ai);
return 0;
}

Expand All @@ -359,7 +360,7 @@ static size_t validate_ai_val(gs1_encoder* const ctx, const char* const ai, cons
continue;

if (complen < part->min) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) data has incorrect length", (int)strlen(entry->ai), ai);
SET_ERR_V(AI_DATA_HAS_INCORRECT_LENGTH, (int)strlen(entry->ai), ai);
return 0;
}

Expand All @@ -384,7 +385,7 @@ static size_t validate_ai_val(gs1_encoder* const ctx, const char* const ai, cons

err = (*l)(compval, &errpos, &errlen);
if (err) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s): %s", (int)strlen(entry->ai), ai, gs1_lint_err_str[err]);
SET_ERR_V(AI_LINTER_ERROR, (int)strlen(entry->ai), ai, gs1_lint_err_str[err]);
ctx->linterErr = err;
errpos += (size_t)(p-start);
snprintf(ctx->linterErrMarkup, sizeof(ctx->linterErrMarkup), "(%.*s)%.*s|%.*s|%.*s",
Expand Down Expand Up @@ -438,18 +439,18 @@ bool gs1_aiValLengthContentCheck(gs1_encoder* const ctx, const char* const ai, c
assert(aiVal);

if (vallen < aiEntryMinLength(entry)) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) value is too short", (int)strlen(entry->ai), ai);
SET_ERR_V(AI_VALUE_IS_TOO_SHORT, (int)strlen(entry->ai), ai);
return false;
}

if (vallen > aiEntryMaxLength(entry)) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) value is too long", (int)strlen(entry->ai), ai);
SET_ERR_V(AI_VALUE_IS_TOO_LONG, (int)strlen(entry->ai), ai);
return false;
}

// Also forbid data "^" characters at this stage so we don't conflate with FNC1
if (memchr(aiVal, '^', vallen) != NULL) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) contains illegal ^ character", (int)strlen(entry->ai), ai);
SET_ERR_V(AI_CONTAINS_ILLEGAL_CARAT_CHARACTER, (int)strlen(entry->ai), ai);
return false;
}

Expand All @@ -471,6 +472,7 @@ bool gs1_parseAIdata(gs1_encoder* const ctx, const char* const aiData, char* con
assert(aiData);

*dataStr = '\0';
ctx->err = gs1_encoder_eNO_ERROR;
*ctx->errMsg = '\0';
ctx->linterErr = GS1_LINTER_OK;
*ctx->linterErrMarkup = '\0';
Expand All @@ -488,7 +490,7 @@ bool gs1_parseAIdata(gs1_encoder* const ctx, const char* const aiData, char* con
ailen = (size_t)(r-p);
entry = gs1_lookupAIentry(ctx, p, ailen);
if (entry == NULL) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "Unrecognised AI: %.*s", (int)ailen, p);
SET_ERR_V(AI_UNRECOGNISED, (int)ailen, p);
goto fail;
}
ai = p;
Expand Down Expand Up @@ -524,7 +526,7 @@ bool gs1_parseAIdata(gs1_encoder* const ctx, const char* const aiData, char* con

// Update the AI data
if (ctx->numAIs >= MAX_AIS) {
strcpy(ctx->errMsg, "Too many AIs");
SET_ERR(TOO_MANY_AIS);
goto fail;
}

Expand All @@ -548,7 +550,7 @@ bool gs1_parseAIdata(gs1_encoder* const ctx, const char* const aiData, char* con
fail:

if (*ctx->errMsg == '\0')
strcpy(ctx->errMsg, "Failed to parse AI data");
SET_ERR(AI_PARSE_FAILED);

DEBUG_PRINT("Parsing AI data failed: %s\n", ctx->errMsg);

Expand All @@ -569,6 +571,7 @@ bool gs1_processAIdata(gs1_encoder* const ctx, const char* const dataStr, const
assert(ctx);
assert(dataStr);

ctx->err = gs1_encoder_eNO_ERROR;
*ctx->errMsg = '\0';
ctx->linterErr = GS1_LINTER_OK;
*ctx->linterErrMarkup = '\0';
Expand All @@ -577,13 +580,13 @@ bool gs1_processAIdata(gs1_encoder* const ctx, const char* const dataStr, const

// Ensure FNC1 in first
if (!*p || *p++ != '^') {
strcpy(ctx->errMsg, "Missing FNC1 in first position");
SET_ERR(MISSING_FNC1_IN_FIRST_POSITION);
return false;
}

// Must have some AI data
if (!*p) {
strcpy(ctx->errMsg, "The AI data is empty");
SET_ERR(AI_DATA_EMPTY);
return false;
}

Expand All @@ -603,7 +606,7 @@ bool gs1_processAIdata(gs1_encoder* const ctx, const char* const dataStr, const
*/
if ((entry = gs1_lookupAIentry(ctx, p, 0)) == NULL ||
(extractAIs && entry == &unknownAI)) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "No known AI is a prefix of: %.4s...", p);
SET_ERR_V(NO_AI_FOR_PREFIX, p);
return false;
}

Expand All @@ -622,7 +625,7 @@ bool gs1_processAIdata(gs1_encoder* const ctx, const char* const dataStr, const
// Add to the aiData
if (extractAIs) {
if (ctx->numAIs >= MAX_AIS) {
strcpy(ctx->errMsg, "Too many AIs");
SET_ERR(TOO_MANY_AIS);
return false;
}
ctx->aiData[ctx->numAIs++] = (struct aiValue) {
Expand All @@ -639,7 +642,7 @@ bool gs1_processAIdata(gs1_encoder* const ctx, const char* const dataStr, const
// After AIs requiring FNC1, we expect to find an FNC1 or be at the end
p += vallen;
if (entry->fnc1 && *p != '^' && *p != '\0') {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) data is too long", (int)strlen(entry->ai), ai);
SET_ERR_V(AI_DATA_IS_TOO_LONG, (int)strlen(entry->ai), ai);
return false;
}

Expand Down Expand Up @@ -735,8 +738,7 @@ static bool validateAImutex(gs1_encoder* const ctx) {
if (!aiExists(ctx, token, ai->ai, &matchedAI))
continue;

snprintf(ctx->errMsg, sizeof(ctx->errMsg), "It is invalid to pair AI (%.*s) with AI (%.*s)",
ai->ailen, ai->ai, matchedAI->ailen, matchedAI->ai);
SET_ERR_V(INVALID_AI_PAIRS, ai->ailen, ai->ai, matchedAI->ailen, matchedAI->ai);
return false;

}
Expand Down Expand Up @@ -804,7 +806,7 @@ static bool validateAIrequisites(gs1_encoder* const ctx) {
}

if (!satisfied) { /* Loop finished without satisfying one of the AI groups in "req" */
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "Required AIs for AI (%.*s) are not satisfied: %s", ai->ailen, ai->ai, reqErr);
SET_ERR_V(REQUIRED_AIS_NOT_SATISFIED, ai->ailen, ai->ai, reqErr);
return false;
}

Expand Down Expand Up @@ -847,7 +849,7 @@ static bool validateAIrepeats(gs1_encoder* const ctx) {

if (ai->ailen == ai2->ailen && strncmp(ai->ai, ai2->ai, ai->ailen) == 0 &&
(ai->vallen != ai2->vallen || strncmp(ai->value, ai2->value, ai->vallen) != 0)) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "Multiple instances of AI (%.*s) have different values", ai->ailen, ai->ai);
SET_ERR_V(INSTANCES_OF_AI_HAVE_DIFFERENT_VALUES, ai->ailen, ai->ai);
return false;
}

Expand Down Expand Up @@ -887,7 +889,7 @@ static bool validateDigSigRequiresSerialisedKey(gs1_encoder* const ctx) {
continue;

if (ai->vallen == aiEntryMinLength(ai->aiEntry)) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "Serial component must be present for AI (%.*s) when used with AI (8030)", ai->ailen, ai->ai);
SET_ERR_V(SERIAL_NOT_PRESENT, ai->ailen, ai->ai);
return false;
}

Expand Down
44 changes: 23 additions & 21 deletions src/c-lib/dl.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* GS1 Barcode Syntax Engine
*
* @author Copyright (c) 2021-2024 GS1 AISBL.
* @author Copyright (c) 2021-2025 GS1 AISBL.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,6 +31,7 @@
#include "enc-private.h"
#include "debug.h"
#include "dl.h"
#include "tr.h"


#define CANONICAL_DL_STEM "https://id.gs1.org"
Expand Down Expand Up @@ -88,7 +89,7 @@ static bool addDLkeyQualifiers(gs1_encoder* const ctx, char*** const dlKeyQualif
if (*pos + req >= *cap) {
char **reallocDLkeyQualifiers = realloc(*dlKeyQualifiers, (*pos + req) * sizeof(char *));
if (!reallocDLkeyQualifiers) {
strcpy(ctx->errMsg, "Failed to reallocate memory for key-qualifiers");
SET_ERR(FAILED_TO_REALLOC_FOR_KEY_QUALIFIERS);
return false;
}
*dlKeyQualifiers = reallocDLkeyQualifiers;
Expand Down Expand Up @@ -145,7 +146,7 @@ bool gs1_populateDLkeyQualifiers(gs1_encoder* const ctx) {

char **dlKeyQualifiers = malloc(cap * sizeof(char *));
if (!dlKeyQualifiers) {
strcpy(ctx->errMsg, "Failed to allocate memory for key-qualifiers");
SET_ERR(FAILED_TO_MALLOC_FOR_KEY_QUALIFIERS);
return false;
}

Expand Down Expand Up @@ -359,6 +360,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
assert(dlData);

*dataStr = '\0';
ctx->err = gs1_encoder_eNO_ERROR;
*ctx->errMsg = '\0';
ctx->linterErr = GS1_LINTER_OK;
*ctx->linterErrMarkup = '\0';
Expand All @@ -368,7 +370,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
p = dlData;

if (strspn(p, uriCharacters) != strlen(p)) {
strcpy(ctx->errMsg, "URI contains illegal characters");
SET_ERR(URI_CONTAINS_ILLEGAL_CHARACTERS);
goto fail;
}

Expand All @@ -381,14 +383,14 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
else if (strlen(p) >= 7 && strncmp(p, "HTTP://", 7) == 0)
p += 7;
else {
strcpy(ctx->errMsg, "Scheme must be http:// or HTTP:// or https:// or HTTPS://");
SET_ERR(URI_CONTAINS_ILLEGAL_SCHEME);
goto fail;
}

DEBUG_PRINT(" Scheme %.*s\n", (int)(p-dlData-3), dlData);

if (((r = strchr(p, '/')) == NULL) || r-p < 1) {
strcpy(ctx->errMsg, "URI must contain a domain and path info");
SET_ERR(URI_MISSING_DOMAIN_AND_PATH_INFO);
goto fail;
}

Expand Down Expand Up @@ -439,7 +441,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
}

if (!dp) {
strcpy(ctx->errMsg, "No GS1 DL keys found in path info");
SET_ERR(NO_GS1_DL_KEYS_FOUND_IN_PATH_INFO);
goto fail;
}

Expand Down Expand Up @@ -472,13 +474,13 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
p = r + strlen(r);

if (p == r) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) value path element is empty", (int)strlen(entry->ai), ai);
SET_ERR_V(AI_VALUE_PATH_ELEMENT_IS_EMPTY, (int)strlen(entry->ai), ai);
goto fail;
}

// Reverse percent encoding
if ((vallen = URIunescape(aival, MAX_AI_VALUE_LEN, r, (size_t)(p-r), false)) == 0) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "Decoded AI (%.*s) from DL path info contains illegal null character", (int)ailen, ai);
SET_ERR_V(DECODED_AI_FROM_DL_PATH_INFO_CONTAINS_ILLEGAL_NULL, (int)ailen, ai);
goto fail;
}

Expand Down Expand Up @@ -510,7 +512,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data

// Update the AI data
if (ctx->numAIs >= MAX_AIS) {
strcpy(ctx->errMsg, "Too many AIs");
SET_ERR(TOO_MANY_AIS);
goto fail;
}

Expand Down Expand Up @@ -561,7 +563,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
ai = p;
ailen = (size_t)(e-p);
if (gs1_allDigits((uint8_t*)p, ailen) && (entry = gs1_lookupAIentry(ctx, p, ailen)) == NULL) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "Unknown AI (%.*s) in query parameters", (int)ailen, p);
SET_ERR_V(UNKNOWN_AI_IN_QUERY_PARAMS, (int)ailen, p);
goto fail;
}

Expand All @@ -574,13 +576,13 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
}

if (r == ++e) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) value query element is empty", (int)strlen(entry->ai), ai);
SET_ERR_V(AI_VALUE_QUERY_ELEMENT_IN_EMPTY, (int)strlen(entry->ai), ai);
goto fail;
}

// Reverse percent encoding
if ((vallen = URIunescape(aival, MAX_AI_VALUE_LEN, e, (size_t)(r-e), true)) == 0) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "Decoded AI (%.*s) value from DL query params contains illegal null character", (int)strlen(entry->ai), ai);
SET_ERR_V(DECODED_AI_VALUE_FROM_QUERY_PARAMS_CONTAINS_ILLEGAL_NULL, (int)strlen(entry->ai), ai);
goto fail;
}

Expand Down Expand Up @@ -615,7 +617,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
add_query_param_to_ai_data:

if (ctx->numAIs >= MAX_AIS) {
strcpy(ctx->errMsg, "Too many AIs");
SET_ERR(TOO_MANY_AIS);
goto fail;
}

Expand Down Expand Up @@ -643,7 +645,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
// Validate that the AI sequence in the path info is a valid
// key-qualifier association
if (!isValidDLpathAIseq(ctx, (const char(*)[MAX_AI_LEN+1])pathAIseq, numPathAIs)) {
strcpy(ctx->errMsg, "The AIs in the path are not a valid key-qualifier sequence for the key");
SET_ERR(INVALID_KEY_QUALIFIER_SEQUENCE);
ret = false;
goto out;
}
Expand All @@ -669,7 +671,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
if (ai2->kind == aiValue_aival &&
ai2->ailen == ai->ailen &&
memcmp(ai2->ai, ai->ai, ai2->ailen) == 0) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) is duplicated", ai->ailen, ai->ai);
SET_ERR_V(DUPLICATE_AI, ai->ailen, ai->ai);
ret = false;
goto out;
}
Expand All @@ -678,7 +680,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
// Check that the AI is a permitted DL URI data attribute
if (ai->aiEntry->dlDataAttr == NO_DATA_ATTR ||
(ai->aiEntry->dlDataAttr == XX_DATA_ATTR && ctx->validationTable[gs1_encoder_vUNKNOWN_AI_NOT_DL_ATTR].enabled)) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) is not a valid DL URI data attribute", ai->ailen, ai->ai);
SET_ERR_V(AI_IS_NOT_VALID_DATA_ATTRIBUTE, ai->ailen, ai->ai);
ret = false;
goto out;
}
Expand All @@ -693,7 +695,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
memcpy(&seq[j+1], &pathAIseq[j], (size_t)(numPathAIs-j) * sizeof(seq[0]));

if (getDLpathAIseqEntry(ctx, (const char(*)[MAX_AI_LEN+1])seq, numPathAIs + 1) != -1) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%s) from query params should be in the path info", seq[j]);
SET_ERR_V(AI_SHOULD_BE_IN_PATH_INFO, seq[j]);
ret = false;
goto out;
}
Expand Down Expand Up @@ -724,7 +726,7 @@ bool gs1_parseDLuri(gs1_encoder* const ctx, char* const dlData, char* const data
fail:

if (*ctx->errMsg == '\0')
strcpy(ctx->errMsg, "Failed to parse DL data");
SET_ERR(DL_URI_PARSE_FAILED);

DEBUG_PRINT("Parsing DL data failed: %s\n", ctx->errMsg);

Expand Down Expand Up @@ -777,7 +779,7 @@ char* gs1_generateDLuri(gs1_encoder* const ctx, const char* const stem) {
}

if (keyEntry == -1) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "Cannot create a DL URI without a primary key AI");
SET_ERR(CANNOT_CREATE_DL_URI_WITHOUT_PRIMARY_KEY_AI);
return NULL;
}

Expand Down Expand Up @@ -918,7 +920,7 @@ char* gs1_generateDLuri(gs1_encoder* const ctx, const char* const stem) {
*/
if (ai->aiEntry->dlDataAttr == NO_DATA_ATTR ||
(ai->aiEntry->dlDataAttr == XX_DATA_ATTR && ctx->validationTable[gs1_encoder_vUNKNOWN_AI_NOT_DL_ATTR].enabled)) {
snprintf(ctx->errMsg, sizeof(ctx->errMsg), "AI (%.*s) is not a valid DL URI data attribute", ai->ailen, ai->ai);
SET_ERR_V(AI_IS_NOT_VALID_DATA_ATTRIBUTE, ai->ailen, ai->ai);
*ctx->outStr = '\0';
return NULL;
}
Expand Down
Loading

0 comments on commit 0bd7b04

Please sign in to comment.