diff --git a/README.md b/README.md index bef57de..88f7682 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/include/error.h b/include/error.h index 620d695..f5ce2ea 100644 --- a/include/error.h +++ b/include/error.h @@ -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, diff --git a/include/evaluator.h b/include/evaluator.h index e14c899..809a93f 100644 --- a/include/evaluator.h +++ b/include/evaluator.h @@ -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 ); diff --git a/include/parser.h b/include/parser.h index e1fb4f0..4e5fe12 100644 --- a/include/parser.h +++ b/include/parser.h @@ -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 ); diff --git a/include/validator.h b/include/validator.h index 61c99d0..05714a6 100644 --- a/include/validator.h +++ b/include/validator.h @@ -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 ); diff --git a/main.c b/main.c index d2ffed9..f7a7cfb 100644 --- a/main.c +++ b/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; } diff --git a/src/evaluator.c b/src/evaluator.c index 328e7db..11dbbd5 100644 --- a/src/evaluator.c +++ b/src/evaluator.c @@ -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; } diff --git a/src/parser.c b/src/parser.c index 7254bb0..8443005 100644 --- a/src/parser.c +++ b/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; diff --git a/src/validator.c b/src/validator.c index 5c48f36..9093339 100644 --- a/src/validator.c +++ b/src/validator.c @@ -2,27 +2,39 @@ #include -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; } diff --git a/test/test_parser.h b/test/test_parser.h index 676b32f..ad1033e 100644 --- a/test/test_parser.h +++ b/test/test_parser.h @@ -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); diff --git a/test/test_validator.h b/test/test_validator.h index 4fb5bc4..1de69ed 100644 --- a/test/test_validator.h +++ b/test/test_validator.h @@ -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); }