schema.go 3.3 KB

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