Migrate to consolidated error handling.

pull/9/head
Joshua Potter 2023-11-25 09:15:30 -07:00
parent ea71a0d661
commit 400388b262
11 changed files with 117 additions and 122 deletions

View File

@ -2,9 +2,9 @@
TODO:
- [ ] Make free-ing data consistent with null pointers.
- [ ] Make error handling consistent.
- [ ] Add documentation throughout (ownership, docstrings, etc.).
- [ ] Have main.c return status code of run.sh.
- [ ] Organize variables in each function to the top?
CLI utility for initializing projects in reproducible ways.

View File

@ -12,7 +12,7 @@ enum ErrorCode {
ERROR_CONFIG_TARGET_INVALID,
ERROR_CONFIG_TARGET_NOT_DIR,
ERROR_PARSER_SPEC_JSON_CANNOT_OPEN,
ERROR_PARSER_SPEC_JSON_INVALID,
ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX,
ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT,

View File

@ -2,16 +2,10 @@
#define _BOOTSTRAP_EVALUATOR_H
#include "config.h"
#include "error.h"
#include "validator.h"
enum SpecEvaluationError {
// Then `run.sh` file could not be found.
SEE_RUN_SH_NOT_FOUND = 1,
// The provided input does not match the expected prompt response type.
SEE_INVALID_PROMPT_RESPONSE,
};
enum SpecEvaluationError evaluate_spec_json(
struct Error *evaluate_spec_json(
const struct Config *const config, const struct DynArray *const prompts
);

View File

@ -3,23 +3,15 @@
#include "cJSON.h"
#include "config.h"
#include "error.h"
enum SpecParseError {
// The `spec.json` file exists but cannot be open.
SPE_CANNOT_OPEN = 1,
// The JSON of the corresponding file is not syntactically valid.
SPE_INVALID_SYNTAX,
};
/*
/**
Reads in the `spec.json` file relative to the paths of the provided @Config.
A spec directory does not necessarily contain a `spec.json` file. If this file
cannot be found, the @parsed pointer is set to NULL with a success return code.
@return: 0 on success and a @SpecParseError otherwise.
*/
enum SpecParseError parse_spec_json(
struct Error *parse_spec_json(
const struct Config *const config, cJSON **parsed
);

View File

@ -3,6 +3,7 @@
#include "cJSON.h"
#include "dyn_array.h"
#include "error.h"
enum FieldType {
FT_STRING = 1,
@ -14,17 +15,7 @@ struct Field {
const char *prompt;
};
enum SpecValidationError {
// The top-level JSON value of a `spec.json` file must be a JSON object.
SVE_TOPLEVEL_NOT_OBJECT = 1,
// The field is not a JSON object.
SVE_FIELD_NOT_OBJECT,
SVE_FIELD_TYPE_INVALID,
SVE_FIELD_TYPE_UNKNOWN,
SVE_FIELD_PROMPT_INVALID,
};
enum SpecValidationError validate_spec_json(
struct Error *validate_spec_json(
const cJSON *const parsed, struct DynArray **fields
);

40
main.c
View File

@ -12,6 +12,7 @@
static int run(const char *root_dir, const char *target) {
int retval = EXIT_FAILURE;
char *cwd = getcwd(0, 0);
if (!root_dir) {
root_dir = getenv("BOOTSTRAP_ROOT_DIR");
@ -26,46 +27,19 @@ static int run(const char *root_dir, const char *target) {
}
cJSON *parsed = 0;
switch (parse_spec_json(config, &parsed)) {
case SPE_CANNOT_OPEN:
fprintf(stderr, "Cannot open `%s/spec.json`.\n", target);
goto cleanup_config;
case SPE_INVALID_SYNTAX:
fprintf(stderr, "`%s/spec.json` is not valid JSON.\n", target);
if ((error = parse_spec_json(config, &parsed))) {
fprintf(stderr, "%s", error->message);
goto cleanup_config;
}
struct DynArray *prompts = 0;
switch (validate_spec_json(parsed, &prompts)) {
case SVE_TOPLEVEL_NOT_OBJECT:
fprintf(stderr, "`%s/spec.json` is not a JSON object.\n", target);
goto cleanup_parsed;
case SVE_FIELD_NOT_OBJECT:
fprintf(
stderr,
"Encountered child in `%s/spec.json` that is not a JSON object.\n",
target
);
goto cleanup_parsed;
case SVE_FIELD_TYPE_INVALID:
fprintf(stderr, "Types must be string values.\n");
goto cleanup_parsed;
case SVE_FIELD_TYPE_UNKNOWN:
fprintf(
stderr, "Encountered an unknown `type` in `%s/spec.json`.\n", target
);
goto cleanup_parsed;
case SVE_FIELD_PROMPT_INVALID:
fprintf(stderr, "Prompts must be string values.\n");
if ((error = validate_spec_json(parsed, &prompts))) {
fprintf(stderr, "%s", error->message);
goto cleanup_parsed;
}
switch (evaluate_spec_json(config, prompts)) {
case SEE_RUN_SH_NOT_FOUND:
fprintf(stderr, "Could not find `%s/run.sh`.\n", target);
goto cleanup_parsed;
case SEE_INVALID_PROMPT_RESPONSE:
fprintf(stderr, "Could not interpret response.\n");
if ((error = evaluate_spec_json(config, prompts))) {
fprintf(stderr, "%s", error->message);
goto cleanup_parsed;
}

View File

@ -7,7 +7,7 @@
#include "path.h"
#include "string_buf.h"
static enum SpecEvaluationError find_run_sh(const struct Config *const config) {
static struct Error *find_run_sh(const struct Config *const config) {
struct stat sb;
const char *segments[] = {config->root_dir, config->target, "run.sh"};
char *filepath =
@ -16,18 +16,25 @@ static enum SpecEvaluationError find_run_sh(const struct Config *const config) {
free(filepath);
if (stat_res == -1 && errno == ENOENT) {
return SEE_RUN_SH_NOT_FOUND;
return ERROR_NEW(
ERROR_EVALUATOR_RUN_SH_NOT_FOUND,
"Could not find ",
config->target,
"/run.sh"
);
}
// TODO: Check run.sh is executable.
return 0;
}
enum SpecEvaluationError evaluate_spec_json(
struct Error *evaluate_spec_json(
const struct Config *const config, const struct DynArray *const prompts
) {
enum SpecEvaluationError retval = find_run_sh(config);
if (retval != 0) {
return retval;
struct Error *error = find_run_sh(config);
if (error) {
return error;
}
if (prompts) {
@ -56,7 +63,6 @@ enum SpecEvaluationError evaluate_spec_json(
// TODO: Want to return this status out.
int status = system(command);
;
return 0;
}

View File

@ -7,31 +7,35 @@
#include "path.h"
static int find_spec_json(const struct Config *const config, FILE **handle) {
static struct Error *find_spec_json(
const struct Config *const config, FILE **handle
) {
const char *segments[] = {config->root_dir, config->target, "spec.json"};
char *filepath =
join_path_segments(sizeof(segments) / sizeof(char *), segments);
int retval = 0;
struct Error *error = 0;
// It is ok if the file does not exist. It is not ok if we couldn't open the
// file for any other reason.
*handle = fopen(filepath, "r");
if (!*handle && errno != ENOENT) {
retval = errno;
error = ERROR_NEW(
ERROR_PARSER_SPEC_JSON_INVALID, config->target, "/spec.json is invalid."
);
}
free(filepath);
return retval;
return error;
}
enum SpecParseError parse_spec_json(
struct Error *parse_spec_json(
const struct Config *const config, cJSON **parsed
) {
FILE *handle = 0;
int retval = find_spec_json(config, &handle);
struct Error *error = find_spec_json(config, &handle);
if (retval != 0) {
return SPE_CANNOT_OPEN;
if (error) {
return error;
}
// The `spec.json` file does not exist.
@ -55,7 +59,10 @@ enum SpecParseError parse_spec_json(
// Can use `cJSON_GetErrorPtr()` to get the actual error message.
if (!*parsed) {
return SPE_INVALID_SYNTAX;
return ERROR_NEW(
ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX,
"The spec.json file contains invalid JSON."
);
}
return 0;

View File

@ -2,27 +2,39 @@
#include <string.h>
static enum SpecValidationError read_field(
const cJSON *const field, struct Field **out
) {
static struct Error *read_field(const cJSON *const field, struct Field **out) {
if (!cJSON_IsObject(field)) {
return SVE_FIELD_NOT_OBJECT;
return ERROR_NEW(
ERROR_VALIDATOR_FIELD_NOT_OBJECT,
"Field \"",
field->string,
"\" is not a JSON object."
);
}
enum SpecValidationError retval = 0;
struct Error *error = 0;
*out = malloc(sizeof(struct Field));
const cJSON *type = cJSON_GetObjectItemCaseSensitive(field, "type");
if (!cJSON_IsString(type)) {
retval = SVE_FIELD_TYPE_INVALID;
error = ERROR_NEW(
ERROR_VALIDATOR_FIELD_TYPE_INVALID,
"Field \"",
field->string,
"\" has non-string \"type\"."
);
goto cleanup;
}
if (strcmp(type->valuestring, "STRING") == 0) {
(*out)->type = FT_STRING;
} else {
retval = SVE_FIELD_TYPE_UNKNOWN;
error = ERROR_NEW(
ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN,
"Field \"",
field->string,
"\" has unknown \"type\"."
);
goto cleanup;
}
@ -30,18 +42,23 @@ static enum SpecValidationError read_field(
if (cJSON_IsString(prompt)) {
(*out)->prompt = prompt->valuestring;
} else {
retval = SVE_FIELD_PROMPT_INVALID;
error = ERROR_NEW(
ERROR_VALIDATOR_FIELD_PROMPT_INVALID,
"Field \"",
field->string,
"\" has non-string \"prompt\"."
);
goto cleanup;
}
return retval;
return error;
cleanup:
free(*out);
return retval;
return error;
}
enum SpecValidationError validate_spec_json(
struct Error *validate_spec_json(
const cJSON *const parsed, struct DynArray **fields
) {
*fields = 0;
@ -52,10 +69,13 @@ enum SpecValidationError validate_spec_json(
}
if (!cJSON_IsObject(parsed)) {
return SVE_TOPLEVEL_NOT_OBJECT;
return ERROR_NEW(
ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT,
"Top-level JSON value in spec.json is not an object."
);
}
enum SpecValidationError retval = 0;
struct Error *error = 0;
// `cJSON_GetArraySize` works because internally JSON objects are stored as
// arrays.
*fields = dyn_array_new(cJSON_GetArraySize(parsed));
@ -63,8 +83,8 @@ enum SpecValidationError validate_spec_json(
cJSON *child = parsed->child;
while (child) {
struct Field *field = 0;
retval = read_field(child, &field);
if (retval) {
error = read_field(child, &field);
if (error) {
goto cleanup;
}
dyn_array_push(*fields, field);
@ -78,5 +98,5 @@ cleanup:
dyn_array_free(*fields);
*fields = 0;
}
return retval;
return error;
}

View File

@ -49,8 +49,8 @@ static void test_parser_missing() {
struct TestParserFixture *fixture = test_parser_setup("no_spec_json");
cJSON *parsed = 0;
enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed);
sput_fail_unless(retval == 0, "no spec.json, success");
struct Error *error = parse_spec_json(&fixture->config, &parsed);
sput_fail_unless(error == 0, "no spec.json, success");
sput_fail_unless(parsed == 0, "no spec.json, no parsed");
test_parser_teardown(fixture);
@ -60,8 +60,8 @@ static void test_parser_minimal() {
struct TestParserFixture *fixture = test_parser_setup("minimal_spec_json");
cJSON *parsed = 0;
enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed);
sput_fail_unless(retval == 0, "minimal spec.json, success");
struct Error *error = parse_spec_json(&fixture->config, &parsed);
sput_fail_unless(error == 0, "minimal spec.json, success");
sput_fail_unless(parsed != 0, "minimal spec.json, parsed");
test_parser_teardown(fixture);
@ -71,10 +71,12 @@ static void test_parser_invalid() {
struct TestParserFixture *fixture = test_parser_setup("invalid_spec_json");
cJSON *parsed = 0;
enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed);
struct Error *error = parse_spec_json(&fixture->config, &parsed);
sput_fail_unless(
retval == SPE_INVALID_SYNTAX, "invalid spec.json, INVALID_SYNTAX"
error->code == ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX,
"invalid spec.json, INVALID_SYNTAX"
);
error_free(error);
sput_fail_unless(parsed == 0, "invalid spec.json, not parsed");
test_parser_teardown(fixture);

View File

@ -30,9 +30,11 @@ static void test_validator_teardown(struct TestValidatorFixture *fixture) {
static void test_validator_toplevel_not_object() {
struct TestValidatorFixture *fixture = test_validator_setup("[]");
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_TOPLEVEL_NOT_OBJECT, "top-level not object");
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(
error->code == ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT, "top-level not object"
);
error_free(error);
test_validator_teardown(fixture);
}
@ -41,9 +43,11 @@ static void test_validator_field_not_object() {
struct TestValidatorFixture *fixture =
test_validator_setup("{\"key\": \"$UNKNOWN\"}");
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_FIELD_NOT_OBJECT, "field not object");
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(
error->code == ERROR_VALIDATOR_FIELD_NOT_OBJECT, "field not object"
);
error_free(error);
test_validator_teardown(fixture);
}
@ -57,9 +61,11 @@ static void test_validator_field_type_invalid() {
"}"
);
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_FIELD_TYPE_INVALID, "field type invalid");
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(
error->code == ERROR_VALIDATOR_FIELD_TYPE_INVALID, "field type invalid"
);
error_free(error);
test_validator_teardown(fixture);
}
@ -73,9 +79,11 @@ static void test_validator_field_type_unknown() {
"}"
);
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_FIELD_TYPE_UNKNOWN, "field type unknown");
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(
error->code == ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN, "field type unknown"
);
error_free(error);
test_validator_teardown(fixture);
}
@ -90,9 +98,11 @@ static void test_validator_field_prompt_invalid() {
"}"
);
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == SVE_FIELD_PROMPT_INVALID, "field prompt invalid");
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(
error->code == ERROR_VALIDATOR_FIELD_PROMPT_INVALID, "field prompt invalid"
);
error_free(error);
test_validator_teardown(fixture);
}
@ -107,9 +117,8 @@ static void test_validator_valid() {
"}"
);
enum SpecValidationError retval =
validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(retval == 0, "valid");
struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
sput_fail_unless(error == 0, "valid");
test_validator_teardown(fixture);
}