feat: add transactional relation service
This commit is contained in:
parent
fbaaac583d
commit
9a9f326c12
5 changed files with 3130 additions and 7 deletions
37
Makefile
37
Makefile
|
|
@ -35,12 +35,9 @@ EVIDENCE_IMPORT_DIALOG_TEST_CFLAGS := \
|
||||||
-Iinclude \
|
-Iinclude \
|
||||||
$(shell $(PKG_CONFIG) --cflags gtk4)
|
$(shell $(PKG_CONFIG) --cflags gtk4)
|
||||||
|
|
||||||
EVIDENCE_IMPORT_DIALOG_TEST_LDFLAGS := \
|
EVIDENCE_IMPORT_DIALOG_TEST_LDFLAGS := $(shell $(PKG_CONFIG) --libs gtk4)
|
||||||
$(shell $(PKG_CONFIG) --libs gtk4)
|
|
||||||
|
|
||||||
EVIDENCE_INTEGRITY_VERIFIER_TEST_CFLAGS := \
|
EVIDENCE_INTEGRITY_VERIFIER_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
$(TEST_CFLAGS) \
|
|
||||||
-Wpedantic
|
|
||||||
ENTITY_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
ENTITY_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
ENTITY_TYPE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
ENTITY_TYPE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
|
|
@ -48,6 +45,10 @@ EVIDENCE_ENTITY_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
RELATION_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
RELATION_RECORD_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
RELATION_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
RELATION_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
RELATION_EVIDENCE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
RELATION_EVIDENCE_DAO_TEST_CFLAGS := $(TEST_CFLAGS) -Wpedantic
|
||||||
|
RELATION_SERVICE_TEST_CFLAGS := \
|
||||||
|
$(TEST_CFLAGS) \
|
||||||
|
-Wpedantic \
|
||||||
|
-DRELATION_SERVICE_ENABLE_TEST_HOOKS
|
||||||
|
|
||||||
SRC := $(shell find src -name "*.c")
|
SRC := $(shell find src -name "*.c")
|
||||||
|
|
||||||
|
|
@ -96,6 +97,7 @@ TEST_EVIDENCE_ENTITY_DAO := tests/test_evidence_entity_dao
|
||||||
TEST_RELATION_RECORD := tests/test_relation_record
|
TEST_RELATION_RECORD := tests/test_relation_record
|
||||||
TEST_RELATION_DAO := tests/test_relation_dao
|
TEST_RELATION_DAO := tests/test_relation_dao
|
||||||
TEST_RELATION_EVIDENCE_DAO := tests/test_relation_evidence_dao
|
TEST_RELATION_EVIDENCE_DAO := tests/test_relation_evidence_dao
|
||||||
|
TEST_RELATION_SERVICE := tests/test_relation_service
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -457,6 +459,24 @@ $(TEST_RELATION_EVIDENCE_DAO): \
|
||||||
src/database/error.c
|
src/database/error.c
|
||||||
$(CC) $(RELATION_EVIDENCE_DAO_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
$(CC) $(RELATION_EVIDENCE_DAO_TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
|
$(TEST_RELATION_SERVICE): \
|
||||||
|
tests/test_relation_service.c \
|
||||||
|
src/core/relation_service.c \
|
||||||
|
src/dao/relation_dao.c \
|
||||||
|
src/dao/relation_evidence_dao.c \
|
||||||
|
src/dao/evidence_dao.c \
|
||||||
|
src/dao/entity_dao.c \
|
||||||
|
src/models/relation_record.c \
|
||||||
|
src/models/evidence_record.c \
|
||||||
|
src/models/entity_record.c \
|
||||||
|
src/database/database.c \
|
||||||
|
src/database/schema.c \
|
||||||
|
src/database/statement.c \
|
||||||
|
src/database/transaction.c \
|
||||||
|
src/database/error.c
|
||||||
|
$(CC) $(RELATION_SERVICE_TEST_CFLAGS) $^ -o $@ \
|
||||||
|
$(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
test: \
|
test: \
|
||||||
$(TEST_NODE) \
|
$(TEST_NODE) \
|
||||||
$(TEST_TREE_MODEL) \
|
$(TEST_TREE_MODEL) \
|
||||||
|
|
@ -498,7 +518,8 @@ test: \
|
||||||
$(TEST_EVIDENCE_ENTITY_DAO) \
|
$(TEST_EVIDENCE_ENTITY_DAO) \
|
||||||
$(TEST_RELATION_RECORD) \
|
$(TEST_RELATION_RECORD) \
|
||||||
$(TEST_RELATION_DAO) \
|
$(TEST_RELATION_DAO) \
|
||||||
$(TEST_RELATION_EVIDENCE_DAO)
|
$(TEST_RELATION_EVIDENCE_DAO) \
|
||||||
|
$(TEST_RELATION_SERVICE)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
|
@ -541,6 +562,7 @@ test: \
|
||||||
@$(TEST_RELATION_RECORD)
|
@$(TEST_RELATION_RECORD)
|
||||||
@$(TEST_RELATION_DAO)
|
@$(TEST_RELATION_DAO)
|
||||||
@$(TEST_RELATION_EVIDENCE_DAO)
|
@$(TEST_RELATION_EVIDENCE_DAO)
|
||||||
|
@$(TEST_RELATION_SERVICE)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -589,6 +611,7 @@ clean:
|
||||||
$(TEST_EVIDENCE_ENTITY_DAO) \
|
$(TEST_EVIDENCE_ENTITY_DAO) \
|
||||||
$(TEST_RELATION_RECORD) \
|
$(TEST_RELATION_RECORD) \
|
||||||
$(TEST_RELATION_DAO) \
|
$(TEST_RELATION_DAO) \
|
||||||
$(TEST_RELATION_EVIDENCE_DAO)
|
$(TEST_RELATION_EVIDENCE_DAO) \
|
||||||
|
$(TEST_RELATION_SERVICE)
|
||||||
|
|
||||||
.PHONY: clean run test
|
.PHONY: clean run test
|
||||||
|
|
|
||||||
133
include/core/relation_service.h
Normal file
133
include/core/relation_service.h
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file relation_service.h
|
||||||
|
* @brief Orchestration transactionnelle de la création des relations.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_RELATION_SERVICE_H
|
||||||
|
#define LABFY_INVESTIGATION_RELATION_SERVICE_H
|
||||||
|
|
||||||
|
#include "database/database.h"
|
||||||
|
#include "models/relation_record.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Service opaque de gestion transactionnelle des relations.
|
||||||
|
*
|
||||||
|
* Le service emprunte la connexion Database reçue à sa création.
|
||||||
|
*/
|
||||||
|
typedef struct RelationService RelationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Catégories d'erreurs produites par RelationService.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Un argument public est invalide.
|
||||||
|
*/
|
||||||
|
RELATION_SERVICE_ERROR_INVALID_ARGUMENT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Une allocation mémoire a échoué.
|
||||||
|
*/
|
||||||
|
RELATION_SERVICE_ERROR_MEMORY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La transaction SQLite n'a pas pu être démarrée.
|
||||||
|
*/
|
||||||
|
RELATION_SERVICE_ERROR_TRANSACTION_BEGIN,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La relation n'a pas pu être insérée.
|
||||||
|
*/
|
||||||
|
RELATION_SERVICE_ERROR_RELATION_INSERT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Une preuve n'a pas pu être associée à la relation.
|
||||||
|
*/
|
||||||
|
RELATION_SERVICE_ERROR_EVIDENCE_LINK,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La transaction SQLite n'a pas pu être validée.
|
||||||
|
*/
|
||||||
|
RELATION_SERVICE_ERROR_TRANSACTION_COMMIT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La transaction SQLite n'a pas pu être annulée.
|
||||||
|
*/
|
||||||
|
RELATION_SERVICE_ERROR_TRANSACTION_ROLLBACK
|
||||||
|
} RelationServiceError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Domaine d'erreur du service.
|
||||||
|
*/
|
||||||
|
#define RELATION_SERVICE_ERROR \
|
||||||
|
relation_service_error_quark()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retourne le domaine d'erreur du service.
|
||||||
|
*/
|
||||||
|
GQuark relation_service_error_quark(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée un service utilisant une connexion Database existante.
|
||||||
|
*
|
||||||
|
* Le service emprunte la connexion et possède les DAO qu'il crée.
|
||||||
|
*
|
||||||
|
* @param database Connexion SQLite ouverte et initialisée.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return Nouveau service, ou NULL en cas d'échec.
|
||||||
|
*/
|
||||||
|
RelationService *relation_service_new(
|
||||||
|
Database *database,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Libère le service.
|
||||||
|
*
|
||||||
|
* Les DAO internes sont libérés, mais la connexion empruntée n'est
|
||||||
|
* jamais fermée.
|
||||||
|
*
|
||||||
|
* Cette fonction accepte NULL.
|
||||||
|
*
|
||||||
|
* @param relation_service Service à libérer.
|
||||||
|
*/
|
||||||
|
void relation_service_free(
|
||||||
|
RelationService *relation_service
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Crée atomiquement une relation et ses associations aux preuves.
|
||||||
|
*
|
||||||
|
* relation_record, evidence_identifiers et les chaînes du tableau sont
|
||||||
|
* empruntés pendant l'appel.
|
||||||
|
*
|
||||||
|
* evidence_identifiers peut être NULL ou vide.
|
||||||
|
*
|
||||||
|
* Un retour TRUE garantit que la relation et toutes ses associations ont
|
||||||
|
* été validées par COMMIT.
|
||||||
|
*
|
||||||
|
* Un retour FALSE signifie que l'opération complète a échoué.
|
||||||
|
*
|
||||||
|
* @param relation_service Service valide.
|
||||||
|
* @param relation_record Relation à insérer.
|
||||||
|
* @param evidence_identifiers Tableau facultatif d'UUID de preuves.
|
||||||
|
* @param error Emplacement facultatif recevant une erreur.
|
||||||
|
*
|
||||||
|
* @return TRUE si l'opération complète est validée.
|
||||||
|
*/
|
||||||
|
gboolean relation_service_create(
|
||||||
|
RelationService *relation_service,
|
||||||
|
const RelationRecord *relation_record,
|
||||||
|
const GPtrArray *evidence_identifiers,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
37
include/core/relation_service_test.h
Normal file
37
include/core/relation_service_test.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file relation_service_test.h
|
||||||
|
* @brief Points d'injection réservés aux tests de RelationService.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_RELATION_SERVICE_TEST_H
|
||||||
|
#define LABFY_INVESTIGATION_RELATION_SERVICE_TEST_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Réinitialise tous les points d'injection du service.
|
||||||
|
*
|
||||||
|
* Disponible uniquement lorsque relation_service.c est compilé avec
|
||||||
|
* RELATION_SERVICE_ENABLE_TEST_HOOKS.
|
||||||
|
*/
|
||||||
|
void relation_service_test_reset_hooks(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Simule l'échec du prochain COMMIT.
|
||||||
|
*/
|
||||||
|
void relation_service_test_set_commit_failure(
|
||||||
|
gboolean enabled
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Simule l'échec du prochain ROLLBACK.
|
||||||
|
*/
|
||||||
|
void relation_service_test_set_rollback_failure(
|
||||||
|
gboolean enabled
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
777
src/core/relation_service.c
Normal file
777
src/core/relation_service.c
Normal file
|
|
@ -0,0 +1,777 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file relation_service.c
|
||||||
|
* @brief Orchestration transactionnelle de la création des relations.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "core/relation_service.h"
|
||||||
|
|
||||||
|
#include "dao/relation_dao.h"
|
||||||
|
#include "dao/relation_evidence_dao.h"
|
||||||
|
#include "database/error.h"
|
||||||
|
#include "database/transaction.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Représentation privée du service.
|
||||||
|
*
|
||||||
|
* La connexion Database est empruntée.
|
||||||
|
* Les deux DAO sont possédés.
|
||||||
|
*/
|
||||||
|
struct RelationService
|
||||||
|
{
|
||||||
|
Database *database;
|
||||||
|
|
||||||
|
RelationDao *relation_dao;
|
||||||
|
RelationEvidenceDao *relation_evidence_dao;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef RELATION_SERVICE_ENABLE_TEST_HOOKS
|
||||||
|
|
||||||
|
#include "core/relation_service_test.h"
|
||||||
|
|
||||||
|
static gboolean relation_service_test_commit_failure =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
static gboolean relation_service_test_rollback_failure =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Consomme un point d'injection à usage unique.
|
||||||
|
*/
|
||||||
|
static gboolean relation_service_test_take_hook(
|
||||||
|
gboolean *hook
|
||||||
|
)
|
||||||
|
{
|
||||||
|
gboolean enabled =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
if (hook == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled =
|
||||||
|
*hook;
|
||||||
|
|
||||||
|
*hook =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void relation_service_test_reset_hooks(void)
|
||||||
|
{
|
||||||
|
relation_service_test_commit_failure =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
relation_service_test_rollback_failure =
|
||||||
|
FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void relation_service_test_set_commit_failure(
|
||||||
|
gboolean enabled
|
||||||
|
)
|
||||||
|
{
|
||||||
|
relation_service_test_commit_failure =
|
||||||
|
enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void relation_service_test_set_rollback_failure(
|
||||||
|
gboolean enabled
|
||||||
|
)
|
||||||
|
{
|
||||||
|
relation_service_test_rollback_failure =
|
||||||
|
enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enregistre une erreur littérale du service.
|
||||||
|
*/
|
||||||
|
static void relation_service_set_error_literal(
|
||||||
|
GError **error,
|
||||||
|
RelationServiceError error_code,
|
||||||
|
const char *message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (error == NULL ||
|
||||||
|
*error != NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR,
|
||||||
|
error_code,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Conserve le contexte d'une erreur provenant d'un DAO.
|
||||||
|
*/
|
||||||
|
static void relation_service_set_nested_error(
|
||||||
|
GError **error,
|
||||||
|
RelationServiceError error_code,
|
||||||
|
const char *context,
|
||||||
|
const GError *nested_error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (error == NULL ||
|
||||||
|
*error != NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_set_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR,
|
||||||
|
error_code,
|
||||||
|
"%s : %s",
|
||||||
|
context,
|
||||||
|
nested_error != NULL
|
||||||
|
? nested_error->message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Conserve le contexte de la dernière erreur Database.
|
||||||
|
*/
|
||||||
|
static void relation_service_set_database_error(
|
||||||
|
GError **error,
|
||||||
|
RelationServiceError error_code,
|
||||||
|
const char *context,
|
||||||
|
const Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *database_message =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (error == NULL ||
|
||||||
|
*error != NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
database_message =
|
||||||
|
database_error_get_message(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
g_set_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR,
|
||||||
|
error_code,
|
||||||
|
"%s : %s",
|
||||||
|
context,
|
||||||
|
database_message != NULL
|
||||||
|
? database_message
|
||||||
|
: "erreur Database inconnue"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie les champs obligatoires du modèle.
|
||||||
|
*/
|
||||||
|
static gboolean relation_service_record_is_valid(
|
||||||
|
const RelationRecord *relation_record
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (relation_record == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relation_record_get_identifier(
|
||||||
|
relation_record
|
||||||
|
) == NULL ||
|
||||||
|
relation_record_get_source_entity_identifier(
|
||||||
|
relation_record
|
||||||
|
) == NULL ||
|
||||||
|
relation_record_get_target_entity_identifier(
|
||||||
|
relation_record
|
||||||
|
) == NULL ||
|
||||||
|
relation_record_get_relation_type(
|
||||||
|
relation_record
|
||||||
|
) == NULL ||
|
||||||
|
relation_record_get_created_at(
|
||||||
|
relation_record
|
||||||
|
) == NULL ||
|
||||||
|
relation_record_get_updated_at(
|
||||||
|
relation_record
|
||||||
|
) == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie les UUID de preuves avant toute transaction.
|
||||||
|
*
|
||||||
|
* Le contrôle des doublons est volontairement effectué sans allocation
|
||||||
|
* supplémentaire. Les listes de preuves liées à une relation restent
|
||||||
|
* normalement courtes et cette validation demeure simple et déterministe.
|
||||||
|
*/
|
||||||
|
static gboolean relation_service_evidence_identifiers_are_valid(
|
||||||
|
const GPtrArray *evidence_identifiers,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
guint evidence_index =
|
||||||
|
0;
|
||||||
|
|
||||||
|
guint previous_index =
|
||||||
|
0;
|
||||||
|
|
||||||
|
if (evidence_identifiers == NULL)
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (evidence_index = 0;
|
||||||
|
evidence_index < evidence_identifiers->len;
|
||||||
|
evidence_index++)
|
||||||
|
{
|
||||||
|
const char *evidence_identifier =
|
||||||
|
g_ptr_array_index(
|
||||||
|
evidence_identifiers,
|
||||||
|
evidence_index
|
||||||
|
);
|
||||||
|
|
||||||
|
if (evidence_identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(
|
||||||
|
evidence_identifier
|
||||||
|
))
|
||||||
|
{
|
||||||
|
relation_service_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La demande contient un identifiant de preuve invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (previous_index = 0;
|
||||||
|
previous_index < evidence_index;
|
||||||
|
previous_index++)
|
||||||
|
{
|
||||||
|
const char *previous_identifier =
|
||||||
|
g_ptr_array_index(
|
||||||
|
evidence_identifiers,
|
||||||
|
previous_index
|
||||||
|
);
|
||||||
|
|
||||||
|
if (g_strcmp0(
|
||||||
|
evidence_identifier,
|
||||||
|
previous_identifier
|
||||||
|
) == 0)
|
||||||
|
{
|
||||||
|
relation_service_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La demande contient plusieurs fois la même preuve."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Valide la transaction, sauf panne simulée par un test.
|
||||||
|
*/
|
||||||
|
static gboolean relation_service_commit_transaction(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
#ifdef RELATION_SERVICE_ENABLE_TEST_HOOKS
|
||||||
|
|
||||||
|
if (relation_service_test_take_hook(
|
||||||
|
&relation_service_test_commit_failure
|
||||||
|
))
|
||||||
|
{
|
||||||
|
database_error_set(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
"Échec de COMMIT simulé par RelationService."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return database_transaction_commit(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Annule la transaction, sauf panne simulée par un test.
|
||||||
|
*/
|
||||||
|
static gboolean relation_service_execute_rollback(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
#ifdef RELATION_SERVICE_ENABLE_TEST_HOOKS
|
||||||
|
|
||||||
|
if (relation_service_test_take_hook(
|
||||||
|
&relation_service_test_rollback_failure
|
||||||
|
))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* La vraie transaction est tout de même annulée afin que le test
|
||||||
|
* ne laisse pas la connexion dans un état inutilisable.
|
||||||
|
*/
|
||||||
|
(void) database_transaction_rollback(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
database_error_set(
|
||||||
|
database,
|
||||||
|
DATABASE_ERROR_SQLITE,
|
||||||
|
"Échec de ROLLBACK simulé par RelationService."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return database_transaction_rollback(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Annule la transaction et conserve l'erreur métier initiale.
|
||||||
|
*/
|
||||||
|
static gboolean relation_service_rollback_transaction(
|
||||||
|
Database *database,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const char *database_message =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
char *primary_error_message =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
if (database == NULL)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_transaction_is_active(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relation_service_execute_rollback(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
database_message =
|
||||||
|
database_error_get_message(
|
||||||
|
database
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error == NULL)
|
||||||
|
{
|
||||||
|
g_warning(
|
||||||
|
"Impossible d'annuler la transaction SQLite : %s",
|
||||||
|
database_message != NULL
|
||||||
|
? database_message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*error != NULL)
|
||||||
|
{
|
||||||
|
primary_error_message =
|
||||||
|
g_strdup(
|
||||||
|
(*error)->message
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_set_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR,
|
||||||
|
RELATION_SERVICE_ERROR_TRANSACTION_ROLLBACK,
|
||||||
|
"Impossible d'annuler la transaction SQLite : %s. "
|
||||||
|
"Cause initiale : %s",
|
||||||
|
database_message != NULL
|
||||||
|
? database_message
|
||||||
|
: "erreur inconnue",
|
||||||
|
primary_error_message != NULL
|
||||||
|
? primary_error_message
|
||||||
|
: "erreur inconnue"
|
||||||
|
);
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
primary_error_message
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GQuark relation_service_error_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string(
|
||||||
|
"relation-service-error-quark"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RelationService *relation_service_new(
|
||||||
|
Database *database,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
RelationService *relation_service =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
RelationDao *relation_dao =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
RelationEvidenceDao *relation_evidence_dao =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
GError *dao_error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (database == NULL)
|
||||||
|
{
|
||||||
|
relation_service_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La connexion Database est obligatoire."
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_dao =
|
||||||
|
relation_dao_new(
|
||||||
|
database,
|
||||||
|
&dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relation_dao == NULL)
|
||||||
|
{
|
||||||
|
relation_service_set_nested_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_MEMORY,
|
||||||
|
"Impossible de créer le DAO des relations",
|
||||||
|
dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_evidence_dao =
|
||||||
|
relation_evidence_dao_new(
|
||||||
|
database,
|
||||||
|
&dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relation_evidence_dao == NULL)
|
||||||
|
{
|
||||||
|
relation_service_set_nested_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_MEMORY,
|
||||||
|
"Impossible de créer le DAO des associations relation-preuve",
|
||||||
|
dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_dao_free(
|
||||||
|
relation_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_service =
|
||||||
|
g_try_new0(
|
||||||
|
RelationService,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relation_service == NULL)
|
||||||
|
{
|
||||||
|
relation_evidence_dao_free(
|
||||||
|
relation_evidence_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_dao_free(
|
||||||
|
relation_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_service_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_MEMORY,
|
||||||
|
"Impossible d'allouer le service de gestion des relations."
|
||||||
|
);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_service->database =
|
||||||
|
database;
|
||||||
|
|
||||||
|
relation_service->relation_dao =
|
||||||
|
relation_dao;
|
||||||
|
|
||||||
|
relation_service->relation_evidence_dao =
|
||||||
|
relation_evidence_dao;
|
||||||
|
|
||||||
|
return relation_service;
|
||||||
|
}
|
||||||
|
|
||||||
|
void relation_service_free(
|
||||||
|
RelationService *relation_service
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (relation_service == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
relation_evidence_dao_free(
|
||||||
|
relation_service->relation_evidence_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_dao_free(
|
||||||
|
relation_service->relation_dao
|
||||||
|
);
|
||||||
|
|
||||||
|
relation_service->relation_evidence_dao =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
relation_service->relation_dao =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
relation_service->database =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
g_free(
|
||||||
|
relation_service
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean relation_service_create(
|
||||||
|
RelationService *relation_service,
|
||||||
|
const RelationRecord *relation_record,
|
||||||
|
const GPtrArray *evidence_identifiers,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GError *dao_error =
|
||||||
|
NULL;
|
||||||
|
|
||||||
|
guint evidence_index =
|
||||||
|
0;
|
||||||
|
|
||||||
|
gboolean transaction_started =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
gboolean success =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
g_return_val_if_fail(
|
||||||
|
error == NULL || *error == NULL,
|
||||||
|
FALSE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relation_service == NULL ||
|
||||||
|
relation_service->database == NULL ||
|
||||||
|
relation_service->relation_dao == NULL ||
|
||||||
|
relation_service->relation_evidence_dao == NULL)
|
||||||
|
{
|
||||||
|
relation_service_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Le service de gestion des relations est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relation_service_record_is_valid(
|
||||||
|
relation_record
|
||||||
|
))
|
||||||
|
{
|
||||||
|
relation_service_set_error_literal(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Le modèle de relation est invalide."
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relation_service_evidence_identifiers_are_valid(
|
||||||
|
evidence_identifiers,
|
||||||
|
error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database_transaction_begin(
|
||||||
|
relation_service->database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
relation_service_set_database_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_TRANSACTION_BEGIN,
|
||||||
|
"Impossible de démarrer la transaction SQLite",
|
||||||
|
relation_service->database
|
||||||
|
);
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction_started =
|
||||||
|
TRUE;
|
||||||
|
|
||||||
|
if (!relation_dao_insert(
|
||||||
|
relation_service->relation_dao,
|
||||||
|
relation_record,
|
||||||
|
&dao_error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
relation_service_set_nested_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_RELATION_INSERT,
|
||||||
|
"Impossible d'enregistrer la relation",
|
||||||
|
dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (evidence_identifiers != NULL)
|
||||||
|
{
|
||||||
|
for (evidence_index = 0;
|
||||||
|
evidence_index < evidence_identifiers->len;
|
||||||
|
evidence_index++)
|
||||||
|
{
|
||||||
|
const char *evidence_identifier =
|
||||||
|
g_ptr_array_index(
|
||||||
|
evidence_identifiers,
|
||||||
|
evidence_index
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relation_evidence_dao_link(
|
||||||
|
relation_service->relation_evidence_dao,
|
||||||
|
relation_record_get_identifier(
|
||||||
|
relation_record
|
||||||
|
),
|
||||||
|
evidence_identifier,
|
||||||
|
&dao_error
|
||||||
|
))
|
||||||
|
{
|
||||||
|
if (error != NULL &&
|
||||||
|
*error == NULL)
|
||||||
|
{
|
||||||
|
g_set_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR,
|
||||||
|
RELATION_SERVICE_ERROR_EVIDENCE_LINK,
|
||||||
|
"Impossible d'associer la preuve %s "
|
||||||
|
"à la relation : %s",
|
||||||
|
evidence_identifier,
|
||||||
|
dao_error != NULL
|
||||||
|
? dao_error->message
|
||||||
|
: "erreur DAO inconnue"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&dao_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relation_service_commit_transaction(
|
||||||
|
relation_service->database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
relation_service_set_database_error(
|
||||||
|
error,
|
||||||
|
RELATION_SERVICE_ERROR_TRANSACTION_COMMIT,
|
||||||
|
"Impossible de valider la transaction SQLite",
|
||||||
|
relation_service->database
|
||||||
|
);
|
||||||
|
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction_started =
|
||||||
|
FALSE;
|
||||||
|
|
||||||
|
success =
|
||||||
|
TRUE;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
g_clear_error(
|
||||||
|
&dao_error
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!success &&
|
||||||
|
transaction_started)
|
||||||
|
{
|
||||||
|
(void) relation_service_rollback_transaction(
|
||||||
|
relation_service->database,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
|
||||||
|
transaction_started =
|
||||||
|
FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
2153
tests/test_relation_service.c
Normal file
2153
tests/test_relation_service.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue