schema.go 2.7 KB

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