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: TODO:
- [ ] Make free-ing data consistent with null pointers. - [ ] Make free-ing data consistent with null pointers.
- [ ] Make error handling consistent.
- [ ] Add documentation throughout (ownership, docstrings, etc.). - [ ] Add documentation throughout (ownership, docstrings, etc.).
- [ ] Have main.c return status code of run.sh. - [ ] 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. CLI utility for initializing projects in reproducible ways.

View File

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

View File

@ -2,16 +2,10 @@
#define _BOOTSTRAP_EVALUATOR_H #define _BOOTSTRAP_EVALUATOR_H
#include "config.h" #include "config.h"
#include "error.h"
#include "validator.h" #include "validator.h"
enum SpecEvaluationError { struct Error *evaluate_spec_json(
// 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(
const struct Config *const config, const struct DynArray *const prompts const struct Config *const config, const struct DynArray *const prompts
); );

View File

@ -3,23 +3,15 @@
#include "cJSON.h" #include "cJSON.h"
#include "config.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. 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 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. 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 const struct Config *const config, cJSON **parsed
); );

View File

@ -3,6 +3,7 @@
#include "cJSON.h" #include "cJSON.h"
#include "dyn_array.h" #include "dyn_array.h"
#include "error.h"
enum FieldType { enum FieldType {
FT_STRING = 1, FT_STRING = 1,
@ -14,17 +15,7 @@ struct Field {
const char *prompt; const char *prompt;
}; };
enum SpecValidationError { struct Error *validate_spec_json(
// 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(
const cJSON *const parsed, struct DynArray **fields 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) { static int run(const char *root_dir, const char *target) {
int retval = EXIT_FAILURE; int retval = EXIT_FAILURE;
char *cwd = getcwd(0, 0); char *cwd = getcwd(0, 0);
if (!root_dir) { if (!root_dir) {
root_dir = getenv("BOOTSTRAP_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; cJSON *parsed = 0;
switch (parse_spec_json(config, &parsed)) { if ((error = parse_spec_json(config, &parsed))) {
case SPE_CANNOT_OPEN: fprintf(stderr, "%s", error->message);
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);
goto cleanup_config; goto cleanup_config;
} }
struct DynArray *prompts = 0; struct DynArray *prompts = 0;
switch (validate_spec_json(parsed, &prompts)) { if ((error = validate_spec_json(parsed, &prompts))) {
case SVE_TOPLEVEL_NOT_OBJECT: fprintf(stderr, "%s", error->message);
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");
goto cleanup_parsed; goto cleanup_parsed;
} }
switch (evaluate_spec_json(config, prompts)) { if ((error = evaluate_spec_json(config, prompts))) {
case SEE_RUN_SH_NOT_FOUND: fprintf(stderr, "%s", error->message);
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");
goto cleanup_parsed; goto cleanup_parsed;
} }

View File

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

View File

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

View File

@ -2,27 +2,39 @@
#include <string.h> #include <string.h>
static enum SpecValidationError read_field( static struct Error *read_field(const cJSON *const field, struct Field **out) {
const cJSON *const field, struct Field **out
) {
if (!cJSON_IsObject(field)) { 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)); *out = malloc(sizeof(struct Field));
const cJSON *type = cJSON_GetObjectItemCaseSensitive(field, "type"); const cJSON *type = cJSON_GetObjectItemCaseSensitive(field, "type");
if (!cJSON_IsString(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; goto cleanup;
} }
if (strcmp(type->valuestring, "STRING") == 0) { if (strcmp(type->valuestring, "STRING") == 0) {
(*out)->type = FT_STRING; (*out)->type = FT_STRING;
} else { } else {
retval = SVE_FIELD_TYPE_UNKNOWN; error = ERROR_NEW(
ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN,
"Field \"",
field->string,
"\" has unknown \"type\"."
);
goto cleanup; goto cleanup;
} }
@ -30,18 +42,23 @@ static enum SpecValidationError read_field(
if (cJSON_IsString(prompt)) { if (cJSON_IsString(prompt)) {
(*out)->prompt = prompt->valuestring; (*out)->prompt = prompt->valuestring;
} else { } else {
retval = SVE_FIELD_PROMPT_INVALID; error = ERROR_NEW(
ERROR_VALIDATOR_FIELD_PROMPT_INVALID,
"Field \"",
field->string,
"\" has non-string \"prompt\"."
);
goto cleanup; goto cleanup;
} }
return retval; return error;
cleanup: cleanup:
free(*out); free(*out);
return retval; return error;
} }
enum SpecValidationError validate_spec_json( struct Error *validate_spec_json(
const cJSON *const parsed, struct DynArray **fields const cJSON *const parsed, struct DynArray **fields
) { ) {
*fields = 0; *fields = 0;
@ -52,10 +69,13 @@ enum SpecValidationError validate_spec_json(
} }
if (!cJSON_IsObject(parsed)) { 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 // `cJSON_GetArraySize` works because internally JSON objects are stored as
// arrays. // arrays.
*fields = dyn_array_new(cJSON_GetArraySize(parsed)); *fields = dyn_array_new(cJSON_GetArraySize(parsed));
@ -63,8 +83,8 @@ enum SpecValidationError validate_spec_json(
cJSON *child = parsed->child; cJSON *child = parsed->child;
while (child) { while (child) {
struct Field *field = 0; struct Field *field = 0;
retval = read_field(child, &field); error = read_field(child, &field);
if (retval) { if (error) {
goto cleanup; goto cleanup;
} }
dyn_array_push(*fields, field); dyn_array_push(*fields, field);
@ -78,5 +98,5 @@ cleanup:
dyn_array_free(*fields); dyn_array_free(*fields);
*fields = 0; *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"); struct TestParserFixture *fixture = test_parser_setup("no_spec_json");
cJSON *parsed = 0; 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 == 0, "no spec.json, success"); sput_fail_unless(error == 0, "no spec.json, success");
sput_fail_unless(parsed == 0, "no spec.json, no parsed"); sput_fail_unless(parsed == 0, "no spec.json, no parsed");
test_parser_teardown(fixture); test_parser_teardown(fixture);
@ -60,8 +60,8 @@ static void test_parser_minimal() {
struct TestParserFixture *fixture = test_parser_setup("minimal_spec_json"); struct TestParserFixture *fixture = test_parser_setup("minimal_spec_json");
cJSON *parsed = 0; 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 == 0, "minimal spec.json, success"); sput_fail_unless(error == 0, "minimal spec.json, success");
sput_fail_unless(parsed != 0, "minimal spec.json, parsed"); sput_fail_unless(parsed != 0, "minimal spec.json, parsed");
test_parser_teardown(fixture); test_parser_teardown(fixture);
@ -71,10 +71,12 @@ static void test_parser_invalid() {
struct TestParserFixture *fixture = test_parser_setup("invalid_spec_json"); struct TestParserFixture *fixture = test_parser_setup("invalid_spec_json");
cJSON *parsed = 0; cJSON *parsed = 0;
enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed); struct Error *error = parse_spec_json(&fixture->config, &parsed);
sput_fail_unless( 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"); sput_fail_unless(parsed == 0, "invalid spec.json, not parsed");
test_parser_teardown(fixture); 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() { static void test_validator_toplevel_not_object() {
struct TestValidatorFixture *fixture = test_validator_setup("[]"); struct TestValidatorFixture *fixture = test_validator_setup("[]");
enum SpecValidationError retval = struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
validate_spec_json(fixture->parsed, &fixture->prompts); sput_fail_unless(
sput_fail_unless(retval == SVE_TOPLEVEL_NOT_OBJECT, "top-level not object"); error->code == ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT, "top-level not object"
);
error_free(error);
test_validator_teardown(fixture); test_validator_teardown(fixture);
} }
@ -41,9 +43,11 @@ static void test_validator_field_not_object() {
struct TestValidatorFixture *fixture = struct TestValidatorFixture *fixture =
test_validator_setup("{\"key\": \"$UNKNOWN\"}"); test_validator_setup("{\"key\": \"$UNKNOWN\"}");
enum SpecValidationError retval = struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
validate_spec_json(fixture->parsed, &fixture->prompts); sput_fail_unless(
sput_fail_unless(retval == SVE_FIELD_NOT_OBJECT, "field not object"); error->code == ERROR_VALIDATOR_FIELD_NOT_OBJECT, "field not object"
);
error_free(error);
test_validator_teardown(fixture); test_validator_teardown(fixture);
} }
@ -57,9 +61,11 @@ static void test_validator_field_type_invalid() {
"}" "}"
); );
enum SpecValidationError retval = struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
validate_spec_json(fixture->parsed, &fixture->prompts); sput_fail_unless(
sput_fail_unless(retval == SVE_FIELD_TYPE_INVALID, "field type invalid"); error->code == ERROR_VALIDATOR_FIELD_TYPE_INVALID, "field type invalid"
);
error_free(error);
test_validator_teardown(fixture); test_validator_teardown(fixture);
} }
@ -73,9 +79,11 @@ static void test_validator_field_type_unknown() {
"}" "}"
); );
enum SpecValidationError retval = struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
validate_spec_json(fixture->parsed, &fixture->prompts); sput_fail_unless(
sput_fail_unless(retval == SVE_FIELD_TYPE_UNKNOWN, "field type unknown"); error->code == ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN, "field type unknown"
);
error_free(error);
test_validator_teardown(fixture); test_validator_teardown(fixture);
} }
@ -90,9 +98,11 @@ static void test_validator_field_prompt_invalid() {
"}" "}"
); );
enum SpecValidationError retval = struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
validate_spec_json(fixture->parsed, &fixture->prompts); sput_fail_unless(
sput_fail_unless(retval == SVE_FIELD_PROMPT_INVALID, "field prompt invalid"); error->code == ERROR_VALIDATOR_FIELD_PROMPT_INVALID, "field prompt invalid"
);
error_free(error);
test_validator_teardown(fixture); test_validator_teardown(fixture);
} }
@ -107,9 +117,8 @@ static void test_validator_valid() {
"}" "}"
); );
enum SpecValidationError retval = struct Error *error = validate_spec_json(fixture->parsed, &fixture->prompts);
validate_spec_json(fixture->parsed, &fixture->prompts); sput_fail_unless(error == 0, "valid");
sput_fail_unless(retval == 0, "valid");
test_validator_teardown(fixture); test_validator_teardown(fixture);
} }