Skip to content

Commit

Permalink
Move validation into canonicalization (#12)
Browse files Browse the repository at this point in the history
* Move validation into canonicalization

* Add acceptance test for invalid variable type

* Add acceptance test for missing URL

* Add acceptance test for missing method

* Add acceptance test for invalid method

* Update changelog
  • Loading branch information
jonathanlloyd committed Apr 22, 2019
1 parent 7cec20d commit 27f52e3
Show file tree
Hide file tree
Showing 12 changed files with 430 additions and 124 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Fixed
- Bug where missing URL / method fields caused a crash
- Issue where only the first validation error is printed

## [0.4.4] - 2019-02-17
### Fixed
Expand Down
61 changes: 4 additions & 57 deletions cmd/rif.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ func main() {
}

validationErrs := validation.ValidateRifYamlFile(yamlStruct, majorVersion)

reqDef, varDefs, canonicalizationErrors := yamlStruct.Canonicalize()
validationErrs = append(validationErrs, canonicalizationErrors...)

if len(validationErrs) > 0 {
errString := ""
for _, err := range validationErrs {
Expand All @@ -120,18 +124,6 @@ func main() {
errorAndExit("Invalid .rif file", errors.New(errString))
}

reqDef := fileversions.RifFileV0{
URL: *yamlStruct.URL,
Method: *yamlStruct.Method,
Headers: yamlStruct.Headers,
Body: &yamlStruct.Body,
}

varDefs, err := makeVariableSchema(yamlStruct.Variables)
if err != nil {
errorAndExit("Invalid .rif file", err)
}

/**
* Step 2 - Parse the variables given over the command line and combine
* them with the default values in the RIF file's variable definitions to
Expand Down Expand Up @@ -280,51 +272,6 @@ func parseInputVars(rawVars []string) (map[string]string, error) {
return vars, nil
}

// makeVariableSchema takes the variables section of the struct used to parse
// the yaml file and returns a decorated version where strings have been
// replaced with enums etc.
func makeVariableSchema(
yamlVarDefs map[string]fileversions.RifYamlVariableV0,
) (map[string]variables.VarDef, error) {
varDefinitions := map[string]variables.VarDef{}
for varName, rawVarDef := range yamlVarDefs {
var varType variables.VarType
switch rawVarDef.Type {
case "boolean":
varType = variables.Boolean
case "number":
varType = variables.Number
case "string":
varType = variables.String
default:
return map[string]variables.VarDef{}, fmt.Errorf(
"variable definition \"%s\" has invalid type \"%s\". "+
"Valid types are boolean, number and string",
varName,
rawVarDef.Type,
)
}

var varDefault interface{}
switch value := rawVarDef.Default.(type) {
case int:
varDefault = int64(value)
case int32:
varDefault = int64(value)
case float32:
varDefault = float64(value)
default:
varDefault = value
}
varDefinitions[varName] = variables.VarDef{
Type: varType,
Default: varDefault,
}
}

return varDefinitions, nil
}

// substituteVariableValues takes a map from variable name to value and a
// parsed RIF file and interpolates the given variables into any template
// strings that exist in the file.
Expand Down
164 changes: 157 additions & 7 deletions internal/app/fileversions/fileversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@

package fileversions

import (
"errors"
"fmt"
"strings"

"github.com/jonathanlloyd/rif/internal/app/variables"
)

var httpMethods = map[string]bool{
"CONNECT": true,
"DELETE": true,
"GET": true,
"HEAD": true,
"OPTIONS": true,
"PATCH": true,
"POST": true,
"PUT": true,
"TRACE": true,
}

// RifFileV0 is the canonical in-memory representation of a parsed rif file
type RifFileV0 struct {
URL string
Method string
Headers map[string]string
Body *string
}

// RifYamlFileV0 is the struct used to unmarshal V0 of the RIF file format
type RifYamlFileV0 struct {
RifVersion *int `yaml:"rif_version"`
Expand All @@ -27,18 +55,140 @@ type RifYamlFileV0 struct {
Variables map[string]RifYamlVariableV0 `yaml:"variables"`
}

// Canonicalize returns the parsed yaml in the canonical internal
// format, decoupling internal logic from the specifics of the yaml schema
// itself
func (y RifYamlFileV0) Canonicalize() (
rFile RifFileV0,
varDefs map[string]variables.VarDef,
validationErrors []error,
) {
canonicalURL, errs := canonicalizeURL(y.URL)
if errs != nil {
validationErrors = append(validationErrors, errs...)
}

canonicalMethod, errs := canonicalizeMethod(y.Method)
if errs != nil {
validationErrors = append(validationErrors, errs...)
}

rFile = RifFileV0{
URL: canonicalURL,
Method: canonicalMethod,
Headers: y.Headers,
Body: &y.Body,
}

varDefs = map[string]variables.VarDef{}
for varName, rawVarDef := range y.Variables {
canonicalVarDef, err := rawVarDef.canonicalize()
if err != nil {
validationErrors = append(
validationErrors,
fmt.Errorf("Variable \"%s\" is invalid: %s", varName, err.Error()),
)
continue
}
varDefs[varName] = canonicalVarDef
}

return rFile, varDefs, validationErrors
}

func canonicalizeURL(yamlURL *string) (
canonicalURL string,
validationErrors []error,
) {
if yamlURL == nil {
return "", []error{errors.New("Field \"URL\" is required")}
}
return *yamlURL, nil
}

func canonicalizeMethod(yamlMethod *string) (
canonicalMethod string,
validationErrors []error,
) {
validationErrors = []error{}

var methodMissing bool
if yamlMethod == nil {
validationErrors = append(
validationErrors,
errors.New("Field \"method\" is required"),
)
methodMissing = true
} else {
canonicalMethod = *yamlMethod
}

canonicalMethod = strings.ToUpper(canonicalMethod)

isValidMethod := httpMethods[canonicalMethod]
if !methodMissing && !isValidMethod {
validationErrors = append(
validationErrors,
fmt.Errorf("Method \"%s\" is invalid", canonicalMethod),
)
}

return canonicalMethod, validationErrors
}

// RifYamlVariableV0 is a struct used to unmarshal the variable schema portion
// of V0 of the RIF file format
type RifYamlVariableV0 struct {
Type string `yaml:"type"`
Default interface{} `yaml:"default"`
}

// RifFileV0 is an in-memory representation of the unversioned beta .rif file
// format
type RifFileV0 struct {
URL string
Method string
Headers map[string]string
Body *string
func (v RifYamlVariableV0) canonicalize() (variables.VarDef, error) {
varType, err := v.canonicalizeType()
if err != nil {
return variables.VarDef{}, err
}
varDefault := v.canonicalizeDefault()

return variables.VarDef{
Type: varType,
Default: varDefault,
}, nil
}

func (v RifYamlVariableV0) canonicalizeType() (variables.VarType, error) {
var varType variables.VarType
switch v.Type {
case "boolean":
varType = variables.Boolean
case "number":
varType = variables.Number
case "string":
varType = variables.String
default:
return varType, fmt.Errorf(
"variable has invalid type \"%s\" "+
"(valid types are boolean, number and string)",
v.Type,
)
}

return varType, nil
}

func (v RifYamlVariableV0) canonicalizeDefault() interface{} {
var d interface{}

switch value := v.Default.(type) {
case int:
d = int64(value)
case int32:
d = int64(value)
case float32:
d = float64(value)
default:
d = value
}

return d
}

0 comments on commit 27f52e3

Please sign in to comment.