github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/cli/compose/schema/schema.go (about)

     1  package schema
     2  
     3  //go:generate go-bindata -pkg schema -nometadata data
     4  
     5  import (
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/xeipuuv/gojsonschema"
    11  )
    12  
    13  type portsFormatChecker struct{}
    14  
    15  func (checker portsFormatChecker) IsFormat(input string) bool {
    16  	// TODO: implement this
    17  	return true
    18  }
    19  
    20  type durationFormatChecker struct{}
    21  
    22  func (checker durationFormatChecker) IsFormat(input string) bool {
    23  	_, err := time.ParseDuration(input)
    24  	return err == nil
    25  }
    26  
    27  func init() {
    28  	gojsonschema.FormatCheckers.Add("expose", portsFormatChecker{})
    29  	gojsonschema.FormatCheckers.Add("ports", portsFormatChecker{})
    30  	gojsonschema.FormatCheckers.Add("duration", durationFormatChecker{})
    31  }
    32  
    33  // Validate uses the jsonschema to validate the configuration
    34  func Validate(config map[string]interface{}) error {
    35  	schemaData, err := Asset("data/config_schema_v3.0.json")
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	schemaLoader := gojsonschema.NewStringLoader(string(schemaData))
    41  	dataLoader := gojsonschema.NewGoLoader(config)
    42  
    43  	result, err := gojsonschema.Validate(schemaLoader, dataLoader)
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	if !result.Valid() {
    49  		return toError(result)
    50  	}
    51  
    52  	return nil
    53  }
    54  
    55  func toError(result *gojsonschema.Result) error {
    56  	err := getMostSpecificError(result.Errors())
    57  	description := getDescription(err)
    58  	return fmt.Errorf("%s %s", err.Field(), description)
    59  }
    60  
    61  func getDescription(err gojsonschema.ResultError) string {
    62  	if err.Type() == "invalid_type" {
    63  		if expectedType, ok := err.Details()["expected"].(string); ok {
    64  			return fmt.Sprintf("must be a %s", humanReadableType(expectedType))
    65  		}
    66  	}
    67  
    68  	return err.Description()
    69  }
    70  
    71  func humanReadableType(definition string) string {
    72  	if definition[0:1] == "[" {
    73  		allTypes := strings.Split(definition[1:len(definition)-1], ",")
    74  		for i, t := range allTypes {
    75  			allTypes[i] = humanReadableType(t)
    76  		}
    77  		return fmt.Sprintf(
    78  			"%s or %s",
    79  			strings.Join(allTypes[0:len(allTypes)-1], ", "),
    80  			allTypes[len(allTypes)-1],
    81  		)
    82  	}
    83  	if definition == "object" {
    84  		return "mapping"
    85  	}
    86  	if definition == "array" {
    87  		return "list"
    88  	}
    89  	return definition
    90  }
    91  
    92  func getMostSpecificError(errors []gojsonschema.ResultError) gojsonschema.ResultError {
    93  	var mostSpecificError gojsonschema.ResultError
    94  
    95  	for _, err := range errors {
    96  		if mostSpecificError == nil {
    97  			mostSpecificError = err
    98  		} else if specificity(err) > specificity(mostSpecificError) {
    99  			mostSpecificError = err
   100  		} else if specificity(err) == specificity(mostSpecificError) {
   101  			// Invalid type errors win in a tie-breaker for most specific field name
   102  			if err.Type() == "invalid_type" && mostSpecificError.Type() != "invalid_type" {
   103  				mostSpecificError = err
   104  			}
   105  		}
   106  	}
   107  
   108  	return mostSpecificError
   109  }
   110  
   111  func specificity(err gojsonschema.ResultError) int {
   112  	return len(strings.Split(err.Field(), "."))
   113  }