Skip to content

Commit

Permalink
feat: improve error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohamed-Hacene committed Jan 17, 2025
1 parent 5132d25 commit 0c55979
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 27 deletions.
45 changes: 23 additions & 22 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2051,7 +2051,9 @@ def _process_uploaded_file(self, dump_file):
logger.error("Invalid JSON format in uploaded file", exc_info=e)
raise
if not import_version == VERSION:
logger.error(f"Import version {import_version} not compatible with current version {VERSION}")
logger.error(
f"Import version {import_version} not compatible with current version {VERSION}"
)
raise ValidationError(
{"file": "importVersionNotCompatibleWithCurrentVersion"}
)
Expand Down Expand Up @@ -2084,17 +2086,18 @@ def _import_objects(self, parsed_data, domain_name: str):
try:
objects = parsed_data.get("objects", None)
if not objects:
return {"message": "No objects to import"}
logger.error("No objects found in the dump")
raise ValidationError({"error": "No objects found in the dump"})

# Validate models and check for domain
models_map = self._get_models_map(objects)
if Folder in models_map.values():
logger.error("Dump contains a domain")
return {"error": "Dump contains a domain"}
raise ValidationError({"error": "Dump contains a domain"})

# Validation phase (outside transaction since it doesn't modify database)
creation_order = self._resolve_dependencies(list(models_map.values()))

for model in creation_order:
self._validate_model_objects(
model=model,
Expand All @@ -2105,17 +2108,13 @@ def _import_objects(self, parsed_data, domain_name: str):

if validation_errors:
logger.error(f"Validation errors: {validation_errors}")
return {"validation_errors": validation_errors}
raise ValidationError({"validation error"})

# Check for missing libraries
for library in required_libraries:
if not LoadedLibrary.objects.filter(urn=library["urn"]).exists():
if not LoadedLibrary.objects.filter(urn=library).exists():
missing_libraries.append(library)

if missing_libraries:
logger.warning(f"Missing libraries: {missing_libraries}")
return {"missing_libraries": missing_libraries}

# Creation phase - wrap in transaction
with transaction.atomic():
# Create base folder and store its ID
Expand All @@ -2135,9 +2134,10 @@ def _import_objects(self, parsed_data, domain_name: str):
return {"message": "Import successful"}

except ValidationError as e:
if missing_libraries == "missingLibraries":
logger.warning(f"Missing libraries: {missing_libraries}")
raise ValidationError({"missing_libraries": missing_libraries})
logger.exception(f"Failed to import objects: {str(e)}")
# The transaction.atomic() context manager will automatically
# roll back all changes if an exception occurs
raise ValidationError({"non_field_errors": "errorOccuredDuringImport"})

def _validate_model_objects(
Expand Down Expand Up @@ -2172,9 +2172,7 @@ def _validate_batch(self, model, batch, validation_errors, required_libraries):
# Handle library objects
if fields.get("library") or model == LoadedLibrary:
if model == LoadedLibrary:
required_libraries.append(
{"urn": fields["urn"], "name": fields["name"]}
)
required_libraries.append(fields["urn"])
continue

# Validate using serializer
Expand Down Expand Up @@ -2314,12 +2312,12 @@ def get_mapped_ids(
urn=fields.get("risk_matrix")
)
fields["ebios_rm_study"] = (
EbiosRMStudy.objects.get(
id=link_dump_database_ids.get(fields["ebios_rm_study"])
)
if fields.get("ebios_rm_study")
else None
EbiosRMStudy.objects.get(
id=link_dump_database_ids.get(fields["ebios_rm_study"])
)
if fields.get("ebios_rm_study")
else None
)

case "complianceassessment":
fields["project"] = Project.objects.get(
Expand Down Expand Up @@ -2407,7 +2405,8 @@ def get_mapped_ids(
fields.pop("assets", []), link_dump_database_ids
),
"compliance_assessment_ids": get_mapped_ids(
fields.pop("compliance_assessments", []), link_dump_database_ids
fields.pop("compliance_assessments", []),
link_dump_database_ids,
),
}
)
Expand Down Expand Up @@ -2576,7 +2575,9 @@ def _set_many_to_many_relations(self, model, obj, many_to_many_map_ids):

case "attackpath":
if stakeholder_ids := many_to_many_map_ids.get("stakeholder_ids"):
obj.stakeholders.set(Stakeholder.objects.filter(id__in=stakeholder_ids))
obj.stakeholders.set(
Stakeholder.objects.filter(id__in=stakeholder_ids)
)

case "operationalscenario":
if threat_ids := many_to_many_map_ids.get("threat_ids"):
Expand Down
4 changes: 3 additions & 1 deletion frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1091,5 +1091,7 @@
"importFolder": "Import domain",
"importFolderHelpText": "Import a domain from a JSON or ZIP file",
"importVersionNotCompatibleWithCurrentVersion": "The import version is not compatible with the current version of the application",
"errorOccuredDuringImport": "An error occurred during the import"
"errorOccuredDuringImport": "An error occurred during the import",
"successfullyImportedFolder": "Folder successfully imported",
"missingLibrariesInImport": "Some libraries are missing, see the list above"
}
4 changes: 3 additions & 1 deletion frontend/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1091,5 +1091,7 @@
"importFolder": "Importer un domaine",
"importFolderHelpText": "Importer un domaine à partir d'un ZIP ou fichier JSON",
"importVersionNotCompatibleWithCurrentVersion": "La version de l'import n'est pas compatible avec la version actuelle de l'application.",
"errorOccuredDuringImport": "Une erreur s'est produite lors de l'import"
"errorOccuredDuringImport": "Une erreur s'est produite lors de l'import",
"successfullyImportedFolder": "Domaine importé avec succès",
"missingLibrariesInImport": "Certaines bibliothèques sont manquantes, voir la liste ci-dessus"
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defaultDeleteFormAction, defaultWriteFormAction, handleErrorResponse } from '$lib/utils/actions';
import { defaultDeleteFormAction, defaultWriteFormAction } from '$lib/utils/actions';
import { BASE_API_URL } from '$lib/utils/constants';
import {
getModelInfo,
Expand All @@ -8,10 +8,13 @@ import {
import { modelSchema } from '$lib/utils/schemas';
import type { ModelInfo } from '$lib/utils/types';
import { type Actions } from '@sveltejs/kit';
import { fail, superValidate, withFiles } from 'sveltekit-superforms';
import { fail, superValidate, withFiles, setError } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';
import type { PageServerLoad } from './$types';
import { setFlash } from 'sveltekit-flash-message/server';
import * as m from '$paraglide/messages';
import { safeTranslate } from '$lib/utils/i18n';

export const load: PageServerLoad = async ({ params, fetch }) => {
const schema = z.object({ id: z.string().uuid() });
Expand Down Expand Up @@ -108,8 +111,34 @@ export const actions: Actions = {
},
body: file
});
const res = await response.json();

if (!response.ok) return await handleErrorResponse({ event, response: response, form });
if (!response.ok && res.missing_libraries) {
setError(form, 'file', m.missingLibrariesInImport());
for (const value of res.missing_libraries) {
setError(form, 'non_field_errors', value);
}
return fail(400, { form });
}

if (!response.ok) {
if (res.error) {
setFlash({ type: 'error', message: safeTranslate(res.error) }, event);
return { form };
}
Object.entries(res).forEach(([key, value]) => {
setError(form, key, safeTranslate(value));
});
return fail(400, { form });
}

setFlash(
{
type: 'success',
message: m.successfullyImportedFolder()
},
event
);

return withFiles({ form });
}
Expand Down

0 comments on commit 0c55979

Please sign in to comment.