ValidateJson.cmake 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. function(validate_json JSON_FILE SCHEMA_NAME JSON_STRING_STR)
  2. unset(${JSON_STRING_STR} PARENT_SCOPE)
  3. message(VERBOSE "Validating ${JSON_FILE} with '${SCHEMA_NAME}' schema")
  4. file(READ ${JSON_FILE} JSON_STRING)
  5. file(READ ${CMAKE_SOURCE_DIR}/data/schemas/${SCHEMA_NAME}.jsonschema SCHEMA_STRING)
  6. string(JSON SCHEMA_ID GET ${SCHEMA_STRING} $id)
  7. set(DEFINITIONS "{}")
  8. file(READ ${CMAKE_SOURCE_DIR}/data/schemas/definitions.jsonschema DEFINITIONS_STRING)
  9. string(JSON DEFINITION_ID GET ${DEFINITIONS_STRING} $id)
  10. string(JSON DEFINITIONS SET ${DEFINITIONS} "${DEFINITION_ID}#" ${DEFINITIONS_STRING})
  11. string(JSON SCHEMA_DEFINITIONS ERROR_VARIABLE JSON_ERROR GET ${SCHEMA_STRING} definitions)
  12. if(${JSON_ERROR} STREQUAL "NOTFOUND")
  13. string(JSON DEFINITIONS SET ${DEFINITIONS} "#" "{}")
  14. string(JSON DEFINITIONS SET ${DEFINITIONS} "#" definitions ${SCHEMA_DEFINITIONS})
  15. # string(JSON DEFINITIONS_LENGTH LENGTH ${SCHEMA_DEFINITIONS})
  16. # math(EXPR MAX "${DEFINITIONS_LENGTH} - 1")
  17. # foreach(IDX RANGE ${MAX})
  18. # string(JSON DEFINITION_NAME MEMBER ${SCHEMA_DEFINITIONS} ${IDX})
  19. # string(JSON DEFINITION GET ${SCHEMA_DEFINITIONS} ${DEFINITION_NAME})
  20. # message(VERBOSE "Loading local definition '${DEFINITION_NAME}'")
  21. # string(JSON DEFINITIONS_STRING SET ${DEFINITIONS_STRING} ${DEFINITION_NAME} ${DEFINITION})
  22. # endforeach()
  23. endif()
  24. validate_object(${JSON_STRING} ${SCHEMA_STRING} OBJECT_ERROR)
  25. if(DEFINED OBJECT_ERROR)
  26. message(FATAL_ERROR ${OBJECT_ERROR})
  27. else()
  28. set(${JSON_STRING_STR} ${JSON_STRING} PARENT_SCOPE)
  29. endif()
  30. endfunction()
  31. function(validate_object JSON_STRING SCHEMA_STRING OBJECT_ERROR_STR)
  32. unset(${OBJECT_ERROR_STR} PARENT_SCOPE)
  33. set(OBJECT_ERROR)
  34. string(JSON PROPERTY_NAME_SCHEMA ERROR_VARIABLE PROPERTY_NAMES_ERROR GET ${SCHEMA_STRING} propertyNames)
  35. string(JSON REQUIRED_PROPERTIES ERROR_VARIABLE REQUIRED_PROPERTIES_ERROR GET ${SCHEMA_STRING} required)
  36. set(REQUIRED_LIST)
  37. if(${REQUIRED_PROPERTIES_ERROR} STREQUAL "NOTFOUND")
  38. string(JSON REQUIRED_LENGTH LENGTH ${REQUIRED_PROPERTIES})
  39. math(EXPR MAX "${REQUIRED_LENGTH} - 1")
  40. foreach(IDX RANGE ${MAX})
  41. string(JSON REQUIRED GET ${REQUIRED_PROPERTIES} ${IDX})
  42. list(APPEND REQUIRED_LIST ${REQUIRED})
  43. endforeach()
  44. endif()
  45. string(JSON NUM_PROPERTIES LENGTH ${JSON_STRING})
  46. math(EXPR MAX "${NUM_PROPERTIES} - 1")
  47. foreach(IDX RANGE ${MAX})
  48. string(JSON PROPERTY_NAME MEMBER ${JSON_STRING} ${IDX})
  49. list(REMOVE_ITEM REQUIRED_LIST ${PROPERTY_NAME})
  50. message(VERBOSE "Validating property '${PROPERTY_NAME}'")
  51. if(${PROPERTY_NAMES_ERROR} STREQUAL "NOTFOUND")
  52. validate_property(${PROPERTY_NAME} ${PROPERTY_NAME_SCHEMA} PROPERTY_NAME_ERROR)
  53. if(DEFINED PROPERTY_NAME_ERROR)
  54. list(APPEND OBJECT_ERROR "${PROPERTY_NAME_ERROR}")
  55. endif()
  56. endif()
  57. string(JSON PROPERTY GET ${JSON_STRING} ${PROPERTY_NAME})
  58. string(JSON SCHEMA_PROPERTIES ERROR_VARIABLE PROPERTIES_ERROR GET ${SCHEMA_STRING} properties ${PROPERTY_NAME})
  59. if(${PROPERTIES_ERROR} STREQUAL "NOTFOUND")
  60. string(JSON PROPERTY_SCHEMA GET ${SCHEMA_STRING} properties ${PROPERTY_NAME})
  61. else()
  62. string(JSON PROPERTY_SCHEMA ERROR_VARIABLE ADDITIONAL_PROPERTIES_ERROR GET ${SCHEMA_STRING} additionalProperties)
  63. if(NOT ${ADDITIONAL_PROPERTIES_ERROR} STREQUAL "NOTFOUND" OR "${PROPERTY_SCHEMA}" STREQUAL "OFF")
  64. list(APPEND OBJECT_ERROR "Additional properties like '${PROPERTY_NAME}' not permitted in '${JSON_STRING}'")
  65. endif()
  66. endif()
  67. validate_property(${PROPERTY} ${PROPERTY_SCHEMA} PROPERTY_ERROR)
  68. if(DEFINED PROPERTY_ERROR)
  69. list(APPEND OBJECT_ERROR "${PROPERTY_ERROR}")
  70. endif()
  71. endforeach()
  72. list(LENGTH REQUIRED_LIST REQUIRED_REMAINING_LENGTH)
  73. if(${REQUIRED_REMAINING_LENGTH} GREATER 0)
  74. list(APPEND OBJECT_ERROR "Required properties not found: ${REQUIRED_LIST}")
  75. endif()
  76. set(${OBJECT_ERROR_STR} ${OBJECT_ERROR} PARENT_SCOPE)
  77. endfunction()
  78. function(validate_property PROPERTY PROPERTY_SCHEMA PROPERTY_ERROR_STR)
  79. unset(${PROPERTY_ERROR_STR} PARENT_SCOPE)
  80. set(PROPERTY_ERROR)
  81. string(JSON PROPERTY_REF ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} $ref)
  82. if(${JSON_ERROR} STREQUAL "NOTFOUND")
  83. string(REPLACE "/" ";" REF_COMPONENTS "${PROPERTY_REF}")
  84. string(JSON PROPERTY_SCHEMA GET ${DEFINITIONS} ${REF_COMPONENTS})
  85. endif()
  86. string(JSON PROPERTY_TYPE ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} type)
  87. if(${JSON_ERROR} STREQUAL "NOTFOUND")
  88. message(VERBOSE "Validating property type '${PROPERTY_TYPE}'")
  89. if(${PROPERTY_TYPE} STREQUAL "object")
  90. validate_object(${PROPERTY} ${PROPERTY_SCHEMA} OBJECT_ERROR)
  91. if(DEFINED OBJECT_ERROR)
  92. list(APPEND PROPERTY_ERROR ${OBJECT_ERROR})
  93. endif()
  94. elseif(${PROPERTY_TYPE} STREQUAL "array")
  95. string(JSON ARRAY_LENGTH LENGTH ${PROPERTY})
  96. string(JSON MAX_ITEMS ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} maxItems)
  97. if(${JSON_ERROR} STREQUAL "NOTFOUND" AND ${ARRAY_LENGTH} GREATER ${MAX_ITEMS})
  98. list(APPEND PROPERTY_ERROR "Number of items in '${PROPERTY}' exceeds maximum ${MAX_ITEMS}")
  99. endif()
  100. string(JSON MIN_ITEMS ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} minItems)
  101. if(${JSON_ERROR} STREQUAL "NOTFOUND" AND ${ARRAY_LENGTH} LESS ${MIN_ITEMS})
  102. list(APPEND PROPERTY_ERROR "Number of items in '${PROPERTY}' is less than ${MIN_ITEMS}")
  103. endif()
  104. string(JSON ITEM_SCHEMA ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} items)
  105. if(${JSON_ERROR} STREQUAL "NOTFOUND")
  106. math(EXPR MAX "${ARRAY_LENGTH} - 1")
  107. foreach(IDX RANGE ${MAX})
  108. string(JSON ITEM GET ${PROPERTY} ${IDX})
  109. validate_property(${ITEM} ${ITEM_SCHEMA} ITEM_ERROR)
  110. if(DEFINED ITEM_ERROR)
  111. list(APPEND PROPERTY_ERROR ${ITEM_ERROR})
  112. endif()
  113. endforeach()
  114. endif()
  115. elseif(${PROPERTY_TYPE} STREQUAL "null")
  116. if(NOT "${PROPERTY}" STREQUAL "null")
  117. list(APPEND PROPERTY_ERROR "Property '${PROPERTY}' is not null'")
  118. endif()
  119. elseif(${PROPERTY_TYPE} STREQUAL "boolean")
  120. if(NOT "${PROPERTY}" STREQUAL "OFF" AND NOT "${PROPERTY}" STREQUAL "ON")
  121. list(APPEND PROPERTY_ERROR "Property '${PROPERTY}' is not a boolean'")
  122. endif()
  123. elseif(${PROPERTY_TYPE} STREQUAL "number")
  124. if(NOT "${PROPERTY}" MATCHES "-?[0-9]+\\.?[0-9]*")
  125. list(APPEND PROPERTY_ERROR "Property '${PROPERTY}' is not a number'")
  126. endif()
  127. elseif(${PROPERTY_TYPE} STREQUAL "integer")
  128. if(NOT "${PROPERTY}" MATCHES "-?[0-9]+")
  129. list(APPEND PROPERTY_ERROR "Property '${PROPERTY}' is not an integer'")
  130. endif()
  131. string(JSON MIN ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} minimum)
  132. if(${JSON_ERROR} STREQUAL "NOTFOUND" AND ${PROPERTY} LESS ${MIN})
  133. list(APPEND PROPERTY_ERROR "Property '${PROPERTY}' is less than the minimum of ${MIN}")
  134. endif()
  135. string(JSON MAX ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} maximum)
  136. if(${JSON_ERROR} STREQUAL "NOTFOUND" AND ${PROPERTY} GREATER ${MAX})
  137. list(APPEND PROPERTY_ERROR "Property '${PROPERTY}' is greater than the maximum of ${MAX}")
  138. endif()
  139. elseif(${PROPERTY_TYPE} STREQUAL "string")
  140. # cmake regex doesn't support {}, so other options might be needed here
  141. string(JSON PATTERN ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} pattern)
  142. if(${JSON_ERROR} STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" MATCHES "${PATTERN}")
  143. list(APPEND PROPERTY_ERROR "Property '${PROPERTY}' does not match '${PATTERN}'")
  144. endif()
  145. string(LENGTH ${PROPERTY} STRING_LENGTH)
  146. string(JSON MIN_LENGTH ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} minLength)
  147. if(${JSON_ERROR} STREQUAL "NOTFOUND" AND ${STRING_LENGTH} LESS ${MIN_LENGTH})
  148. list(APPEND PROPERTY_ERROR "Length of property '${PROPERTY}' is less than the minimum of ${MIN_LENGTH}")
  149. endif()
  150. string(JSON MAX_LENGTH ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} maxLength)
  151. if(${JSON_ERROR} STREQUAL "NOTFOUND" AND ${STRING_LENGTH} GREATER ${MAX_LENGTH})
  152. list(APPEND PROPERTY_ERROR "Length of property '${PROPERTY}' is greater than the maximum of ${MAX_LENGTH}")
  153. endif()
  154. string(JSON ENUM_LIST ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} enum)
  155. if(${JSON_ERROR} STREQUAL "NOTFOUND")
  156. set(FOUND_IN_ENUM_LIST FALSE)
  157. string(JSON ENUM_LENGTH LENGTH ${ENUM_LIST})
  158. math(EXPR MAX "${ENUM_LENGTH} - 1")
  159. foreach(IDX RANGE ${MAX})
  160. string(JSON ENUM GET ${PROPERTY_SCHEMA} enum ${IDX})
  161. if(${ENUM} STREQUAL ${PROPERTY})
  162. set(FOUND_IN_ENUM_LIST TRUE)
  163. endif()
  164. endforeach()
  165. if(NOT ${FOUND_IN_ENUM_LIST})
  166. list(APPEND PROPERTY_ERROR "Property '${PROPERTY}' is not defined in the schema's enum: ${ENUM_LIST}")
  167. endif()
  168. endif()
  169. else()
  170. message(VERBOSE "Unknown type '${PROPERTY_TYPE}'")
  171. endif()
  172. else()
  173. string(JSON PROPERTY_ONEOF ERROR_VARIABLE JSON_ERROR GET ${PROPERTY_SCHEMA} oneOf)
  174. if(${JSON_ERROR} STREQUAL "NOTFOUND")
  175. set(TYPE_SUCCESS FALSE)
  176. string(JSON NUM_ONEOF LENGTH ${PROPERTY_ONEOF})
  177. math(EXPR MAX "${NUM_ONEOF} - 1")
  178. set(ONEOF_ERRORS)
  179. foreach(IDX RANGE ${MAX})
  180. string(JSON PROPERTY_SCHEMA GET ${PROPERTY_ONEOF} ${IDX})
  181. validate_property(${PROPERTY} ${PROPERTY_SCHEMA} ONEOF_ERROR)
  182. if(NOT DEFINED ONEOF_ERROR)
  183. set(TYPE_SUCCESS TRUE)
  184. else()
  185. list(APPEND ONEOF_ERRORS "${ONEOF_ERROR}\n")
  186. endif()
  187. endforeach()
  188. if(NOT TYPE_SUCCESS)
  189. list(APPEND PROPERTY_ERROR "Could not validate oneOf type '${PROPERTY}' :\n${ONEOF_ERRORS}")
  190. endif()
  191. endif()
  192. endif()
  193. set(${PROPERTY_ERROR_STR} ${PROPERTY_ERROR} PARENT_SCOPE)
  194. endfunction()