diff --git a/DESCRIPTION b/DESCRIPTION index 7d24900..7522ea5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: pacta.workflow.utils Title: Utility functions for PACTA workflows -Version: 0.0.0.9009 +Version: 0.0.0.9010 Authors@R: c(person(given = "Alex", family = "Axthelm", diff --git a/NAMESPACE b/NAMESPACE index 631c0b5..b197c48 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,7 @@ # Generated by roxygen2: do not edit by hand export(export_manifest) +export(parse_raw_params) importFrom(logger,log_debug) importFrom(logger,log_error) importFrom(logger,log_info) diff --git a/R/parse_json_params.R b/R/parse_params.R similarity index 100% rename from R/parse_json_params.R rename to R/parse_params.R diff --git a/R/parse_raw_params.R b/R/parse_raw_params.R new file mode 100644 index 0000000..c3c2fa0 --- /dev/null +++ b/R/parse_raw_params.R @@ -0,0 +1,148 @@ +#' parse_raw_params +#' +#' Parse and validate JSON parameters, including optionally validation raw +#' parameters prior to inheriting. +#' +#' @param json JSON string +#' @param inheritence_search_paths path to directory in which to search for +#' inheritance files. See `parse_params` for details. +#' @param schema_file JSON Schema file to validate parameters against. See +#' `parse_params` for details. +#' @param raw_schema_file JSON Schema file to validate raw parameters against. +#' See `jsonvalidate::json_validate` for details. +#' @return list of parameters +#' @examples +# nolint start +#' product_schema <- '{ +#' "$schema": "http://json-schema.org/draft-04/schema#", +#' "title": "Product", +#' "description": "A product from Acme\'s catalog", +#' "type": "object", +#' "properties": { +#' "id": { +#' "description": "The unique identifier for a product", +#' "type": "integer" +#' }, +#' "name": { +#' "description": "Name of the product", +#' "type": "string" +#' }, +#' "price": { +#' "type": "number", +#' "minimum": 0, +#' "exclusiveMinimum": true +#' }, +#' "tags": { +#' "type": "array", +#' "items": { +#' "type": "string" +#' }, +#' "minItems": 1, +#' "uniqueItems": true +#' } +#' }, +#' "required": ["id", "name", "price"] +#' }' +#' schema_dir <- withr::local_tempdir() +#' schema_file <- file.path(schema_dir, "product.json") +#' writeLines(product_schema, schema_file) +#' raw_schema <- '{ +#' "$schema": "http://json-schema.org/draft-04/schema#", +#' "title": "rawProduct", +#' "description": "Valid Input params for product", +#' "type": "object", +#' "properties": { +#' "inherit": { +#' "description": "Valid keys for inheritence", +#' "type": "string", +#' "enum": [ +#' "base01", +#' "base02" +#' ] +#' } +#' }, +#' "anyOf": [ +#' { +#' "required": [ +#' "inherit" +#' ] +#' }, +#' { +#' "$ref": "product.json" +#' } +#' ] +#' }' +#' raw_schema_file <- file.path(schema_dir, "rawProduct.json") +#' writeLines(raw_schema, raw_schema_file) +#' json_string <- '{ +#' "id": 1, +#' "price": 12.50, +#' "inherit": "base01" +#' } ' +#' base_params_dir <- withr::local_tempdir() +#' base_01_string <- '{ +#' "name": "A green door", +#' "tags": ["home", "green"], +#' "supplier": "ACME Doors" +#' }' +#' writeLines( +#' base_01_string, +#' file.path(base_params_dir, "base01.json") +#' ) +#' results <- parse_raw_params( +#' json = json_string, +#' schema_file = schema_file, +#' inheritence_search_paths = base_params_dir, +#' raw_schema_file = raw_schema_file +#' ) +#' results +# nolint end +#' @export +parse_raw_params <- function( + json, + inheritence_search_paths = NULL, + schema_file = NULL, + raw_schema_file = NULL +) { + # Read Params + log_trace("Processing input parameters.") + if (length(json) == 0L || all(json == "")) { + log_error("No parameters specified.") + stop("No parameters specified.") + } + + if (is.null(raw_schema_file)) { + log_debug( + "No raw schema file specified. ", + "Skipping raw parameter validation." + ) + } else { + log_trace("Validating raw input parameters.") + raw_input_validation_results <- jsonvalidate::json_validate( + json = json, + schema = raw_schema_file, + verbose = TRUE, + greedy = FALSE, + engine = "ajv" + ) + if (raw_input_validation_results) { + log_trace("Raw input parameters are valid.") + } else { + log_error( + "Invalid raw input parameters. ", + "Must include \"inherit\" key, or match full schema.", + "See schema for details.", + raw_schema_file + ) + stop("Invalid raw input parameters.") + } + } + + params <- parse_params( + json = json, + inheritence_search_paths = inheritence_search_paths, + schema_file = schema_file + ) + + return(params) +} diff --git a/man/parse_raw_params.Rd b/man/parse_raw_params.Rd new file mode 100644 index 0000000..e58a99f --- /dev/null +++ b/man/parse_raw_params.Rd @@ -0,0 +1,117 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/parse_raw_params.R +\name{parse_raw_params} +\alias{parse_raw_params} +\title{parse_raw_params} +\usage{ +parse_raw_params( + json, + inheritence_search_paths = NULL, + schema_file = NULL, + raw_schema_file = NULL +) +} +\arguments{ +\item{json}{JSON string} + +\item{inheritence_search_paths}{path to directory in which to search for +inheritance files. See \code{parse_params} for details.} + +\item{schema_file}{JSON Schema file to validate parameters against. See +\code{parse_params} for details.} + +\item{raw_schema_file}{JSON Schema file to validate raw parameters against. +See \code{jsonvalidate::json_validate} for details.} +} +\value{ +list of parameters +} +\description{ +Parse and validate JSON parameters, including optionally validation raw +parameters prior to inheriting. +} +\examples{ +product_schema <- '{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Product", + "description": "A product from Acme\'s catalog", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "integer" + }, + "name": { + "description": "Name of the product", + "type": "string" + }, + "price": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["id", "name", "price"] +}' +schema_dir <- withr::local_tempdir() +schema_file <- file.path(schema_dir, "product.json") +writeLines(product_schema, schema_file) +raw_schema <- '{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "rawProduct", + "description": "Valid Input params for product", + "type": "object", + "properties": { + "inherit": { + "description": "Valid keys for inheritence", + "type": "string", + "enum": [ + "base01", + "base02" + ] + } + }, + "anyOf": [ + { + "required": [ + "inherit" + ] + }, + { + "$ref": "product.json" + } + ] +}' +raw_schema_file <- file.path(schema_dir, "rawProduct.json") +writeLines(raw_schema, raw_schema_file) +json_string <- '{ + "id": 1, + "price": 12.50, + "inherit": "base01" +} ' +base_params_dir <- withr::local_tempdir() +base_01_string <- '{ + "name": "A green door", + "tags": ["home", "green"], + "supplier": "ACME Doors" + }' +writeLines( + base_01_string, + file.path(base_params_dir, "base01.json") +) +results <- parse_raw_params( + json = json_string, + schema_file = schema_file, + inheritence_search_paths = base_params_dir, + raw_schema_file = raw_schema_file +) +results +} diff --git a/tests/testthat/test-parse_raw_params.R b/tests/testthat/test-parse_raw_params.R new file mode 100644 index 0000000..3d78b0e --- /dev/null +++ b/tests/testthat/test-parse_raw_params.R @@ -0,0 +1,288 @@ +## save current settings so that we can reset later +threshold <- logger::log_threshold() +appender <- logger::log_appender() +layout <- logger::log_layout() +on.exit({ + ## reset logger settings + logger::log_threshold(threshold) + logger::log_layout(layout) + logger::log_appender(appender) +}) + +logger::log_appender(logger::appender_stdout) +logger::log_threshold(logger::FATAL) +logger::log_layout(logger::layout_simple) + +test_that("No inheritence, no raw param validation, pass as string", { + json_string <- '{ + "id": 1, + "name": "A green door", + "price": 12.50, + "tags": ["home", "green"] + }' + results <- parse_raw_params(json_string) + expect_identical( + object = results, + expected = list( + id = 1L, + name = "A green door", + price = 12.5, + tags = c("home", "green") + ) + ) +}) + +test_that("Empty param string fails", { + json_string <- "" + expect_error( + parse_raw_params(json = json_string), + "No parameters specified." + ) +}) + +test_that("NULL param string fails", { + json_string <- NULL + expect_error( + parse_raw_params(json = json_string), + "No parameters specified." + ) +}) + +test_that("multiple empty param string fails", { + json_string <- rep("", 3L) + expect_error( + parse_raw_params(json = json_string), + "No parameters specified." + ) +}) + +product_schema <- '{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Product", + "description": "A product from Acme\'s catalog", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a product", + "type": "integer" + }, + "name": { + "description": "Name of the product", + "type": "string" + }, + "price": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "required": ["id", "name", "price"] +}' +schema_dir <- withr::local_tempdir() +schema_file <- file.path(schema_dir, "product.json") +writeLines(product_schema, schema_file) + +test_that("No inheritence, schema validation works", { + json_string <- '{ + "id": 1, + "name": "A green door", + "price": 12.50, + "tags": ["home", "green"] + }' + results <- parse_raw_params( + json = json_string, + schema_file = schema_file + ) + expect_identical( + object = results, + expected = list( + id = 1L, + name = "A green door", + price = 12.5, + tags = c("home", "green") + ) + ) +}) + +base_params_dir <- withr::local_tempdir() +base_01_string <- '{ + "name": "A green door", + "tags": ["home", "green"], + "supplier": "ACME Doors" + }' +writeLines( + base_01_string, + file.path(base_params_dir, "base01.json") +) +base_02_string <- '{ + "name": "A blue door", + "tags": ["home", "blue"], + "supplier": "FooBar Doors" + }' +writeLines( + base_02_string, + file.path(base_params_dir, "base02.json") +) + +test_that("simple inheritence, schema validation works", { + json_string <- '{ + "id": 1, + "price": 12.50, + "inherit": "base01" + }' + results <- parse_raw_params( + json = json_string, + schema_file = schema_file, + inheritence_search_paths = base_params_dir + ) + expect_identical( + object = results, + expected = list( + name = "A green door", + tags = c("home", "green"), + supplier = "ACME Doors", + id = 1L, + price = 12.5 + ) + ) +}) + +test_that("simple inheritence 2, schema validation works", { + json_string <- '{ + "id": 1, + "price": 12.50, + "inherit": "base02" + }' + results <- parse_raw_params( + json = json_string, + schema_file = schema_file, + inheritence_search_paths = base_params_dir + ) + expect_identical( + object = results, + expected = list( + name = "A blue door", + tags = c("home", "blue"), + supplier = "FooBar Doors", + id = 1L, + price = 12.5 + ) + ) +}) + +raw_schema <- '{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "rawProduct", + "description": "Valid Input params for product", + "type": "object", + "properties": { + "inherit": { + "description": "Valid keys for inheritence", + "type": "string", + "enum": [ + "base01", + "base02" + ] + } + }, + "anyOf": [ + { + "required": [ + "inherit" + ] + }, + { + "$ref": "product.json" + } + ] +}' +raw_schema_file <- file.path(schema_dir, "rawProduct.json") +writeLines(raw_schema, raw_schema_file) + +test_that("No inheritence, raw validation works", { + json_string <- '{ + "id": 1, + "name": "A green door", + "price": 12.50, + "tags": ["home", "green"] + }' + results <- parse_raw_params( + json = json_string, + schema_file = schema_file, + raw_schema_file = raw_schema_file + ) + expect_identical( + object = results, + expected = list( + id = 1L, + name = "A green door", + price = 12.5, + tags = c("home", "green") + ) + ) +}) + +test_that("simple inheritence, raw validation works - inherit", { + json_string <- '{ + "id": 1, + "price": 12.50, + "inherit": "base01" + }' + results <- parse_raw_params( + json = json_string, + schema_file = schema_file, + inheritence_search_paths = base_params_dir, + raw_schema_file = raw_schema_file + ) + expect_identical( + object = results, + expected = list( + name = "A green door", + tags = c("home", "green"), + supplier = "ACME Doors", + id = 1L, + price = 12.5 + ) + ) +}) + +test_that("raw validation errors correctly, no inherit field", { + json_string <- '{ + "id": 1, + "price": 12.50 + }' + expect_error( + parse_raw_params( + json = json_string, + schema_file = schema_file, + inheritence_search_paths = base_params_dir, + raw_schema_file = raw_schema_file + ), + "Invalid raw input parameters." + ) +}) + +test_that("raw validation errors correctly, invalid inherit field", { + json_string <- '{ + "id": 1, + "price": 12.50, + "inherit": "base03" + }' + expect_error( + parse_raw_params( + json = json_string, + schema_file = schema_file, + inheritence_search_paths = base_params_dir, + raw_schema_file = raw_schema_file + ), + "Invalid raw input parameters." + ) +})