labfy-investigation/src/models/relation_record.c

697 lines
14 KiB
C

/******************************************************************************
* @file relation_record.c
* @brief Implémentation du modèle représentant une relation persistée.
******************************************************************************/
#include "models/relation_record.h"
#include <string.h>
/**
* @brief Représentation privée d'une relation.
*/
struct RelationRecord
{
char *identifier;
char *source_entity_identifier;
char *target_entity_identifier;
char *relation_type;
gint64 relation_type_identifier;
char *relation_type_code;
char *label;
char *justification;
gint confidence;
char *created_at;
char *updated_at;
RelationStatus status;
};
/**
* @brief Copie et nettoie une chaîne.
*/
static char *relation_record_duplicate_trimmed(
const char *value
)
{
char *copy =
NULL;
if (value == NULL)
{
return NULL;
}
copy =
g_strdup(
value
);
g_strstrip(
copy
);
return copy;
}
/**
* @brief Normalise un champ facultatif.
*
* Une chaîne vide après nettoyage devient NULL.
*/
static char *relation_record_duplicate_optional(
const char *value
)
{
char *copy =
NULL;
copy =
relation_record_duplicate_trimmed(
value
);
if (copy != NULL &&
copy[0] == '\0')
{
g_free(
copy
);
copy =
NULL;
}
return copy;
}
/**
* @brief Vérifie qu'une chaîne obligatoire est présente.
*/
static gboolean relation_record_required_value_is_valid(
const char *value
)
{
return value != NULL &&
value[0] != '\0';
}
/**
* @brief Vérifie une date UTC stricte au format YYYY-MM-DDTHH:MM:SSZ.
*/
static gboolean relation_record_timestamp_is_valid(
const char *timestamp
)
{
GDateTime *date_time =
NULL;
char *normalized_timestamp =
NULL;
gboolean valid =
FALSE;
gsize character_index =
0;
if (timestamp == NULL ||
strlen(timestamp) != 20U)
{
return FALSE;
}
if (timestamp[4] != '-' ||
timestamp[7] != '-' ||
timestamp[10] != 'T' ||
timestamp[13] != ':' ||
timestamp[16] != ':' ||
timestamp[19] != 'Z')
{
return FALSE;
}
for (character_index = 0;
character_index < 20U;
character_index++)
{
if (character_index == 4U ||
character_index == 7U ||
character_index == 10U ||
character_index == 13U ||
character_index == 16U ||
character_index == 19U)
{
continue;
}
if (!g_ascii_isdigit(
timestamp[character_index]
))
{
return FALSE;
}
}
date_time =
g_date_time_new_from_iso8601(
timestamp,
NULL
);
if (date_time == NULL)
{
return FALSE;
}
normalized_timestamp =
g_date_time_format(
date_time,
"%Y-%m-%dT%H:%M:%SZ"
);
valid =
normalized_timestamp != NULL &&
g_strcmp0(
normalized_timestamp,
timestamp
) == 0;
g_free(
normalized_timestamp
);
g_date_time_unref(
date_time
);
return valid;
}
/**
* @brief Vérifie qu'un statut peut être persisté.
*/
static gboolean relation_record_status_is_valid(
RelationStatus status
)
{
return status == RELATION_STATUS_ACTIVE ||
status == RELATION_STATUS_ARCHIVED ||
status == RELATION_STATUS_DELETED ||
status == RELATION_STATUS_DISPUTED;
}
GQuark relation_record_error_quark(void)
{
return g_quark_from_static_string(
"relation-record-error-quark"
);
}
RelationRecord *relation_record_new(
const char *identifier,
const char *source_entity_identifier,
const char *target_entity_identifier,
const char *relation_type,
const char *label,
const char *justification,
gint confidence,
const char *created_at,
const char *updated_at,
RelationStatus status,
GError **error
)
{
RelationRecord *relation_record =
NULL;
char *identifier_copy =
NULL;
char *source_entity_identifier_copy =
NULL;
char *target_entity_identifier_copy =
NULL;
char *relation_type_copy =
NULL;
char *label_copy =
NULL;
char *justification_copy =
NULL;
char *created_at_copy =
NULL;
char *updated_at_copy =
NULL;
g_return_val_if_fail(
error == NULL || *error == NULL,
NULL
);
identifier_copy =
relation_record_duplicate_trimmed(
identifier
);
if (!relation_record_required_value_is_valid(
identifier_copy
) ||
!g_uuid_string_is_valid(
identifier_copy
))
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_IDENTIFIER,
"L'identifiant de la relation n'est pas un UUID valide."
);
goto cleanup;
}
source_entity_identifier_copy =
relation_record_duplicate_trimmed(
source_entity_identifier
);
if (!relation_record_required_value_is_valid(
source_entity_identifier_copy
) ||
!g_uuid_string_is_valid(
source_entity_identifier_copy
))
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_SOURCE_IDENTIFIER,
"L'identifiant de l'entité source n'est pas un UUID valide."
);
goto cleanup;
}
target_entity_identifier_copy =
relation_record_duplicate_trimmed(
target_entity_identifier
);
if (!relation_record_required_value_is_valid(
target_entity_identifier_copy
) ||
!g_uuid_string_is_valid(
target_entity_identifier_copy
))
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_TARGET_IDENTIFIER,
"L'identifiant de l'entité cible n'est pas un UUID valide."
);
goto cleanup;
}
if (g_strcmp0(
source_entity_identifier_copy,
target_entity_identifier_copy
) == 0)
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_IDENTICAL_ENTITIES,
"Une relation ne peut pas relier une entité à elle-même."
);
goto cleanup;
}
relation_type_copy =
relation_record_duplicate_trimmed(
relation_type
);
if (!relation_record_required_value_is_valid(
relation_type_copy
))
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_TYPE,
"Le type de relation est obligatoire."
);
goto cleanup;
}
if (confidence < 0 ||
confidence > 100)
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_CONFIDENCE,
"Le niveau de confiance doit être compris entre 0 et 100."
);
goto cleanup;
}
created_at_copy =
relation_record_duplicate_trimmed(
created_at
);
if (!relation_record_timestamp_is_valid(
created_at_copy
))
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_DATE,
"La date de création de la relation est invalide."
);
goto cleanup;
}
updated_at_copy =
relation_record_duplicate_trimmed(
updated_at
);
if (!relation_record_timestamp_is_valid(
updated_at_copy
))
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_DATE,
"La date de modification de la relation est invalide."
);
goto cleanup;
}
if (!relation_record_status_is_valid(
status
))
{
g_set_error_literal(
error,
RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_STATUS,
"Le statut de la relation est invalide."
);
goto cleanup;
}
label_copy =
relation_record_duplicate_optional(
label
);
justification_copy =
relation_record_duplicate_optional(
justification
);
relation_record =
g_new0(
RelationRecord,
1
);
relation_record->identifier =
identifier_copy;
relation_record->source_entity_identifier =
source_entity_identifier_copy;
relation_record->target_entity_identifier =
target_entity_identifier_copy;
relation_record->relation_type =
relation_type_copy;
relation_record->label =
label_copy;
relation_record->justification =
justification_copy;
relation_record->confidence =
confidence;
relation_record->created_at =
created_at_copy;
relation_record->updated_at =
updated_at_copy;
relation_record->status =
status;
identifier_copy = NULL;
source_entity_identifier_copy = NULL;
target_entity_identifier_copy = NULL;
relation_type_copy = NULL;
label_copy = NULL;
justification_copy = NULL;
created_at_copy = NULL;
updated_at_copy = NULL;
cleanup:
g_free(
updated_at_copy
);
g_free(
created_at_copy
);
g_free(
justification_copy
);
g_free(
label_copy
);
g_free(
relation_type_copy
);
g_free(
target_entity_identifier_copy
);
g_free(
source_entity_identifier_copy
);
g_free(
identifier_copy
);
return relation_record;
}
RelationRecord *relation_record_new_canonical(
const char *identifier, const char *source_entity_identifier,
const char *target_entity_identifier, gint64 relation_type_identifier,
const char *relation_type_code, const char *relation_type_label,
const char *label, const char *justification, gint confidence,
const char *created_at, const char *updated_at, RelationStatus status,
GError **error)
{
RelationRecord *record = NULL;
if (relation_type_identifier <= 0)
{
g_set_error_literal(error, RELATION_RECORD_ERROR,
RELATION_RECORD_ERROR_INVALID_TYPE,
"L'identifiant du type canonique est invalide.");
return NULL;
}
record = relation_record_new(identifier, source_entity_identifier,
target_entity_identifier, relation_type_label, label, justification,
confidence, created_at, updated_at, status, error);
if (record == NULL) return NULL;
record->relation_type_identifier = relation_type_identifier;
record->relation_type_code = relation_record_duplicate_optional(
relation_type_code);
return record;
}
void relation_record_free(
RelationRecord *relation_record
)
{
if (relation_record == NULL)
{
return;
}
g_free(
relation_record->updated_at
);
g_free(
relation_record->created_at
);
g_free(
relation_record->justification
);
g_free(
relation_record->label
);
g_free(
relation_record->relation_type
);
g_free(relation_record->relation_type_code);
g_free(
relation_record->target_entity_identifier
);
g_free(
relation_record->source_entity_identifier
);
g_free(
relation_record->identifier
);
g_free(
relation_record
);
}
const char *relation_record_get_identifier(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->identifier
: NULL;
}
const char *relation_record_get_source_entity_identifier(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->source_entity_identifier
: NULL;
}
const char *relation_record_get_target_entity_identifier(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->target_entity_identifier
: NULL;
}
const char *relation_record_get_relation_type(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? (relation_record->relation_type_code != NULL
? relation_record->relation_type_code
: relation_record->relation_type)
: NULL;
}
gint64 relation_record_get_relation_type_identifier(
const RelationRecord *relation_record)
{
return relation_record != NULL ? relation_record->relation_type_identifier : 0;
}
const char *relation_record_get_relation_type_code(
const RelationRecord *relation_record)
{
return relation_record != NULL ? relation_record->relation_type_code : NULL;
}
const char *relation_record_get_relation_type_label(
const RelationRecord *relation_record)
{
return relation_record != NULL ? relation_record->relation_type : NULL;
}
const char *relation_record_get_label(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->label
: NULL;
}
const char *relation_record_get_justification(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->justification
: NULL;
}
gint relation_record_get_confidence(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->confidence
: 0;
}
const char *relation_record_get_created_at(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->created_at
: NULL;
}
const char *relation_record_get_updated_at(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->updated_at
: NULL;
}
RelationStatus relation_record_get_status(
const RelationRecord *relation_record
)
{
return relation_record != NULL
? relation_record->status
: RELATION_STATUS_UNKNOWN;
}