Migrate to consolidated error handling.
parent
ea71a0d661
commit
400388b262
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
@ -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
40
main.c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
25
src/parser.c
25
src/parser.c
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue