[ffigen] Add ffigen yaml schema (#586)

diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md
index 7418a01..c6919af 100644
--- a/pkgs/ffigen/CHANGELOG.md
+++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 9.0.0
+
+- Added a JSON schema for FFIgen config files.
+
 # 8.0.2
 
 - Fixed invalid code generated due to zero-length arrays in structs/union.
diff --git a/pkgs/ffigen/example/c_json/config.yaml b/pkgs/ffigen/example/c_json/config.yaml
index 08c5891..3d61cbd 100644
--- a/pkgs/ffigen/example/c_json/config.yaml
+++ b/pkgs/ffigen/example/c_json/config.yaml
@@ -1,3 +1,5 @@
+# yaml-language-server: $schema=../../ffigen.schema.json
+
 output: 'cjson_generated_bindings.dart'
 name: 'CJson'
 description: 'Holds bindings to cJSON.'
diff --git a/pkgs/ffigen/example/ffinative/config.yaml b/pkgs/ffigen/example/ffinative/config.yaml
index 18aeb3f..397dbba 100644
--- a/pkgs/ffigen/example/ffinative/config.yaml
+++ b/pkgs/ffigen/example/ffinative/config.yaml
@@ -1,3 +1,5 @@
+# yaml-language-server: $schema=../../ffigen.schema.json
+
 name: NativeLibrary
 ffi-native:
   # asset: 'assetname' # (optional)
@@ -7,4 +9,4 @@
   entry-points:
     - 'headers/example.h'
 preamble: |
-  // ignore_for_file: deprecated_member_use
\ No newline at end of file
+  // ignore_for_file: deprecated_member_use
diff --git a/pkgs/ffigen/example/libclang-example/config.yaml b/pkgs/ffigen/example/libclang-example/config.yaml
index f6517c0..69c111e 100644
--- a/pkgs/ffigen/example/libclang-example/config.yaml
+++ b/pkgs/ffigen/example/libclang-example/config.yaml
@@ -1,3 +1,5 @@
+# yaml-language-server: $schema=../../ffigen.schema.json
+
 output: 'generated_bindings.dart'
 
 # This will sort the bindings alphabetically.
diff --git a/pkgs/ffigen/example/objective_c/config.yaml b/pkgs/ffigen/example/objective_c/config.yaml
index 94764ca..b44cc69 100644
--- a/pkgs/ffigen/example/objective_c/config.yaml
+++ b/pkgs/ffigen/example/objective_c/config.yaml
@@ -1,3 +1,5 @@
+# yaml-language-server: $schema=../../ffigen.schema.json
+
 name: AVFAudio
 description: Bindings for AVFAudio.
 language: objc
diff --git a/pkgs/ffigen/example/shared_bindings/ffigen_configs/a.yaml b/pkgs/ffigen/example/shared_bindings/ffigen_configs/a.yaml
index 55a720e..a23faa1 100644
--- a/pkgs/ffigen/example/shared_bindings/ffigen_configs/a.yaml
+++ b/pkgs/ffigen/example/shared_bindings/ffigen_configs/a.yaml
@@ -2,6 +2,8 @@
 # for details. All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
 
+# yaml-language-server: $schema=../../../ffigen.schema.json
+
 name: NativeLibraryA
 description: Bindings to `headers/a.h`.
 output: '../lib/generated/a_gen.dart'
diff --git a/pkgs/ffigen/example/shared_bindings/ffigen_configs/a_shared_base.yaml b/pkgs/ffigen/example/shared_bindings/ffigen_configs/a_shared_base.yaml
index 0adcc20..aa4f1b1 100644
--- a/pkgs/ffigen/example/shared_bindings/ffigen_configs/a_shared_base.yaml
+++ b/pkgs/ffigen/example/shared_bindings/ffigen_configs/a_shared_base.yaml
@@ -2,6 +2,8 @@
 # for details. All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
 
+# yaml-language-server: $schema=../../../ffigen.schema.json
+
 name: NativeLibraryASharedB
 description: Bindings to `headers/a.h` with shared definitions from `headers/base.h`.
 output: '../lib/generated/a_shared_b_gen.dart'
diff --git a/pkgs/ffigen/example/shared_bindings/ffigen_configs/base.yaml b/pkgs/ffigen/example/shared_bindings/ffigen_configs/base.yaml
index 474d0f9..da03542 100644
--- a/pkgs/ffigen/example/shared_bindings/ffigen_configs/base.yaml
+++ b/pkgs/ffigen/example/shared_bindings/ffigen_configs/base.yaml
@@ -2,6 +2,8 @@
 # for details. All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
 
+# yaml-language-server: $schema=../../../ffigen.schema.json
+
 name: NativeLibraryBase
 description: Bindings to `headers/base.h`.
 output:
diff --git a/pkgs/ffigen/example/simple/config.yaml b/pkgs/ffigen/example/simple/config.yaml
index 82ed925..f12c349 100644
--- a/pkgs/ffigen/example/simple/config.yaml
+++ b/pkgs/ffigen/example/simple/config.yaml
@@ -1,3 +1,5 @@
+# yaml-language-server: $schema=../../ffigen.schema.json
+
 name: NativeLibrary
 description: Bindings to `headers/example.h`.
 output: 'generated_bindings.dart'
diff --git a/pkgs/ffigen/example/swift/config.yaml b/pkgs/ffigen/example/swift/config.yaml
index e1a8322..6082e97 100644
--- a/pkgs/ffigen/example/swift/config.yaml
+++ b/pkgs/ffigen/example/swift/config.yaml
@@ -1,3 +1,5 @@
+# yaml-language-server: $schema=../../ffigen.schema.json
+
 name: SwiftLibrary
 description: Bindings for swift_api.
 language: objc
diff --git a/pkgs/ffigen/ffigen.schema.json b/pkgs/ffigen/ffigen.schema.json
new file mode 100644
index 0000000..a557a87
--- /dev/null
+++ b/pkgs/ffigen/ffigen.schema.json
@@ -0,0 +1,524 @@
+{
+  "$id": "https://json.schemastore.org/ffigen",
+  "$comment": "This file is generated. To regenerate run: dart tool/generate_json_schema.dart in github.com/dart-lang/ffigen",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "type": "object",
+  "additionalProperties": false,
+  "properties": {
+    "llvm-path": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      }
+    },
+    "output": {
+      "$oneOf": [
+        {
+          "$ref": "#/$defs/filePath"
+        },
+        {
+          "type": "object",
+          "additionalProperties": false,
+          "properties": {
+            "bindings": {
+              "$ref": "#/$defs/filePath"
+            },
+            "symbol-file": {
+              "type": "object",
+              "additionalProperties": false,
+              "properties": {
+                "output": {
+                  "$ref": "#/$defs/filePath"
+                },
+                "import-path": {
+                  "type": "string"
+                }
+              },
+              "required": [
+                "output",
+                "import-path"
+              ]
+            }
+          },
+          "required": [
+            "bindings"
+          ]
+        }
+      ]
+    },
+    "language": {
+      "enum": [
+        "c",
+        "objc"
+      ]
+    },
+    "headers": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "entry-points": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "include-directives": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        }
+      },
+      "required": [
+        "entry-points"
+      ]
+    },
+    "compiler-opts": {
+      "$oneOf": [
+        {
+          "type": "string"
+        },
+        {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        }
+      ]
+    },
+    "compiler-opts-automatic": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "macos": {
+          "type": "object",
+          "additionalProperties": false,
+          "properties": {
+            "include-c-standard-library": {
+              "type": "boolean"
+            }
+          }
+        }
+      }
+    },
+    "library-imports": {
+      "type": "object",
+      "patternProperties": {
+        ".*": {
+          "type": "string"
+        }
+      }
+    },
+    "functions": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        },
+        "member-rename": {
+          "$ref": "#/$defs/memberRename"
+        },
+        "symbol-address": {
+          "$ref": "#/$defs/includeExclude"
+        },
+        "expose-typedefs": {
+          "$ref": "#/$defs/includeExclude"
+        },
+        "leaf": {
+          "$ref": "#/$defs/includeExclude"
+        },
+        "variadic-arguments": {
+          "type": "object",
+          "patternProperties": {
+            ".*": {
+              "type": "array",
+              "items": {
+                "$oneOf": [
+                  {
+                    "type": "array",
+                    "items": {
+                      "type": "string"
+                    }
+                  },
+                  {
+                    "type": "object",
+                    "additionalProperties": false,
+                    "properties": {
+                      "types": {
+                        "type": "array",
+                        "items": {
+                          "type": "string"
+                        }
+                      },
+                      "postfix": {
+                        "type": "string"
+                      }
+                    },
+                    "required": [
+                      "types"
+                    ]
+                  }
+                ]
+              }
+            }
+          }
+        }
+      }
+    },
+    "structs": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        },
+        "member-rename": {
+          "$ref": "#/$defs/memberRename"
+        },
+        "dependency-only": {
+          "$ref": "#/$defs/dependencyOnly"
+        },
+        "pack": {
+          "type": "object",
+          "patternProperties": {
+            ".*": {
+              "enum": [
+                "none",
+                1,
+                2,
+                4,
+                8,
+                16
+              ]
+            }
+          }
+        }
+      }
+    },
+    "unions": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        },
+        "member-rename": {
+          "$ref": "#/$defs/memberRename"
+        },
+        "dependency-only": {
+          "$ref": "#/$defs/dependencyOnly"
+        }
+      }
+    },
+    "enums": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        },
+        "member-rename": {
+          "$ref": "#/$defs/memberRename"
+        }
+      }
+    },
+    "unnamed-enums": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        }
+      }
+    },
+    "globals": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        },
+        "symbol-address": {
+          "$ref": "#/$defs/includeExclude"
+        }
+      }
+    },
+    "macros": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        }
+      }
+    },
+    "typedefs": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        }
+      }
+    },
+    "objc-interfaces": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "rename": {
+          "$ref": "#/$defs/rename"
+        },
+        "member-rename": {
+          "$ref": "#/$defs/memberRename"
+        },
+        "module": {
+          "$ref": "#/$defs/objcInterfaceModule"
+        }
+      }
+    },
+    "import": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "symbol-files": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        }
+      }
+    },
+    "type-map": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "typedefs": {
+          "$ref": "#/$defs/mappedTypes"
+        },
+        "structs": {
+          "$ref": "#/$defs/mappedTypes"
+        },
+        "unions": {
+          "$ref": "#/$defs/mappedTypes"
+        },
+        "native-types": {
+          "$ref": "#/$defs/mappedTypes"
+        }
+      }
+    },
+    "exclude-all-by-default": {
+      "type": "boolean"
+    },
+    "sort": {
+      "type": "boolean"
+    },
+    "use-supported-typedefs": {
+      "type": "boolean"
+    },
+    "comments": {
+      "$oneOf": [
+        {
+          "type": "boolean"
+        },
+        {
+          "type": "object",
+          "additionalProperties": false,
+          "properties": {
+            "style": {
+              "enum": [
+                "doxygen",
+                "any"
+              ]
+            },
+            "length": {
+              "enum": [
+                "brief",
+                "full"
+              ]
+            }
+          }
+        }
+      ]
+    },
+    "name": {
+      "$ref": "#/$defs/publicDartClass"
+    },
+    "description": {
+      "$ref": "#/$defs/nonEmptyString"
+    },
+    "preamble": {
+      "type": "string"
+    },
+    "use-dart-handle": {
+      "type": "boolean"
+    },
+    "ffi-native": {
+      "$oneOf": [
+        {
+          "enum": [
+            null
+          ]
+        },
+        {
+          "type": "object",
+          "additionalProperties": false,
+          "properties": {
+            "asset": {
+              "type": "string"
+            }
+          },
+          "required": [
+            "asset"
+          ]
+        }
+      ]
+    }
+  },
+  "required": [
+    "output",
+    "headers"
+  ],
+  "$defs": {
+    "filePath": {
+      "type": "string",
+      "description": "A file path"
+    },
+    "fullMatchOrRegexpList": {
+      "type": "array",
+      "items": {
+        "type": "string"
+      }
+    },
+    "rename": {
+      "type": "object",
+      "patternProperties": {
+        ".*": {
+          "type": "string"
+        }
+      }
+    },
+    "memberRename": {
+      "type": "object",
+      "patternProperties": {
+        ".*": {
+          "type": "object",
+          "patternProperties": {
+            ".*": {
+              "type": "string"
+            }
+          }
+        }
+      }
+    },
+    "includeExclude": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "include": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        },
+        "exclude": {
+          "$ref": "#/$defs/fullMatchOrRegexpList"
+        }
+      }
+    },
+    "dependencyOnly": {
+      "enum": [
+        "full",
+        "opaque"
+      ]
+    },
+    "objcInterfaceModule": {
+      "type": "object",
+      "patternProperties": {
+        ".*": {
+          "type": "string"
+        }
+      }
+    },
+    "mappedTypes": {
+      "type": "object",
+      "patternProperties": {
+        ".*": {
+          "type": "object",
+          "additionalProperties": false,
+          "properties": {
+            "lib": {
+              "type": "string"
+            },
+            "c-type": {
+              "type": "string"
+            },
+            "dart-type": {
+              "type": "string"
+            }
+          }
+        }
+      }
+    },
+    "publicDartClass": {
+      "type": "string",
+      "description": "A public dart class name.",
+      "pattern": "^[a-zA-Z]+[_a-zA-Z0-9]*$"
+    },
+    "nonEmptyString": {
+      "type": "string",
+      "pattern": ".+"
+    }
+  }
+}
\ No newline at end of file
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart
index 12a7e69..5109a0c 100644
--- a/pkgs/ffigen/lib/src/config_provider/config.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -12,6 +12,7 @@
 import 'package:yaml/yaml.dart';
 
 import '../strings.dart' as strings;
+import 'config_spec.dart';
 import 'config_types.dart';
 import 'spec_utils.dart';
 
@@ -22,12 +23,10 @@
 /// Handles validation, extraction of configurations from a yaml file.
 class Config {
   /// Input filename.
-  String? get filename => _filename;
-  String? _filename;
+  final String? filename;
 
   /// Package config.
-  PackageConfig? get packageConfig => _packageConfig;
-  PackageConfig? _packageConfig;
+  final PackageConfig? packageConfig;
 
   /// Location for llvm/lib folder.
   String get libclangDylib => _libclangDylib;
@@ -176,23 +175,22 @@
   FfiNativeConfig get ffiNativeConfig => _ffiNativeConfig;
   late FfiNativeConfig _ffiNativeConfig;
 
-  Config._(this._filename, this._packageConfig);
+  Config._({required this.filename, required this.packageConfig});
 
   /// Create config from Yaml map.
   factory Config.fromYaml(YamlMap map,
       {String? filename, PackageConfig? packageConfig}) {
-    final configspecs = Config._(filename, packageConfig);
+    final config = Config._(filename: filename, packageConfig: packageConfig);
     _logger.finest('Config Map: $map');
 
-    final specs = configspecs._getSpecs();
-
-    final result = configspecs._checkConfigs(map, specs);
+    final ffigenConfigSpec = config._getRootConfigSpec();
+    final result = ffigenConfigSpec.validate(map);
     if (!result) {
       throw FormatException('Invalid configurations provided.');
     }
 
-    configspecs._extract(map, specs);
-    return configspecs;
+    ffigenConfigSpec.extract(map);
+    return config;
   }
 
   /// Create config from a file.
@@ -204,6 +202,12 @@
         filename: file.path, packageConfig: packageConfig);
   }
 
+  /// Returns the root ConfigSpec object.
+  static ConfigSpec getsRootConfigSpec() {
+    final configspecs = Config._(filename: null, packageConfig: null);
+    return configspecs._getRootConfigSpec();
+  }
+
   /// Add compiler options for clang. If [highPriority] is true these are added
   /// to the front of the list.
   void addCompilerOpts(String compilerOpts, {bool highPriority = false}) {
@@ -215,363 +219,698 @@
     }
   }
 
-  /// Validates Yaml according to given specs.
-  bool _checkConfigs(YamlMap map, Map<List<String>, Specification> specs) {
-    var result = true;
-    for (final key in specs.keys) {
-      final spec = specs[key];
-      if (checkKeyInYaml(key, map)) {
-        result = result && spec!.validator(key, getKeyValueFromYaml(key, map));
-      } else if (spec!.requirement == Requirement.yes) {
-        _logger.severe("Key '$key' is required.");
-        result = false;
-      } else if (spec.requirement == Requirement.prefer) {
-        _logger.warning("Prefer adding Key '$key' to your config.");
-      }
-    }
-    // Warn about unknown keys.
-    warnUnknownKeys(specs.keys.toList(), map);
-
-    return result;
+  ConfigSpec _getRootConfigSpec() {
+    return HeterogeneousMapConfigSpec(
+      entries: [
+        HeterogeneousMapEntry(
+          key: strings.llvmPath,
+          valueConfigSpec: ListConfigSpec<String, String>(
+            childConfigSpec: StringConfigSpec(),
+            transform: (node) => llvmPathExtractor(node.value),
+          ),
+          defaultValue: (node) => findDylibAtDefaultLocations(),
+          resultOrDefault: (node) => _libclangDylib = node.value as String,
+        ),
+        HeterogeneousMapEntry(
+            key: strings.output,
+            required: true,
+            valueConfigSpec: OneOfConfigSpec(
+              childConfigSpecs: [
+                _filePathStringConfigSpec(),
+                _outputFullConfigSpec(),
+              ],
+              transform: (node) =>
+                  outputExtractor(node.value, filename, packageConfig),
+              result: (node) {
+                _output = (node.value as OutputConfig).output;
+                _symbolFile = (node.value as OutputConfig).symbolFile;
+              },
+            )),
+        HeterogeneousMapEntry(
+          key: strings.language,
+          valueConfigSpec: EnumConfigSpec(
+            allowedValues: {strings.langC, strings.langObjC},
+            transform: (node) {
+              if ((node.value == strings.langObjC)) {
+                _logger.severe(
+                    'Objective C support is EXPERIMENTAL. The API may change '
+                    'in a breaking way without notice.');
+                return Language.objc;
+              } else {
+                return Language.c;
+              }
+            },
+          ),
+          defaultValue: (node) => Language.c,
+          resultOrDefault: (node) => _language = node.value as Language,
+        ),
+        HeterogeneousMapEntry(
+            key: strings.headers,
+            required: true,
+            valueConfigSpec: HeterogeneousMapConfigSpec<List<String>, Headers>(
+              entries: [
+                HeterogeneousMapEntry(
+                  key: strings.entryPoints,
+                  valueConfigSpec: ListConfigSpec<String, List<String>>(
+                      childConfigSpec: StringConfigSpec()),
+                  required: true,
+                ),
+                HeterogeneousMapEntry(
+                  key: strings.includeDirectives,
+                  valueConfigSpec: ListConfigSpec<String, List<String>>(
+                      childConfigSpec: StringConfigSpec()),
+                ),
+              ],
+              transform: (node) => headersExtractor(node.value, filename),
+              result: (node) => _headers = node.value,
+            )),
+        HeterogeneousMapEntry(
+          key: strings.compilerOpts,
+          valueConfigSpec: OneOfConfigSpec<List<String>, List<String>>(
+            childConfigSpecs: [
+              StringConfigSpec(
+                transform: (node) => [node.value],
+              ),
+              ListConfigSpec<String, List<String>>(
+                  childConfigSpec: StringConfigSpec())
+            ],
+            transform: (node) => compilerOptsExtractor(node.value),
+          ),
+          defaultValue: (node) => <String>[],
+          resultOrDefault: (node) => _compilerOpts = node.value as List<String>,
+        ),
+        HeterogeneousMapEntry(
+            key: strings.compilerOptsAuto,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                HeterogeneousMapEntry(
+                  key: strings.macos,
+                  valueConfigSpec: HeterogeneousMapConfigSpec(
+                    entries: [
+                      HeterogeneousMapEntry(
+                        key: strings.includeCStdLib,
+                        valueConfigSpec: BoolConfigSpec(),
+                        defaultValue: (node) => true,
+                      )
+                    ],
+                  ),
+                )
+              ],
+              transform: (node) => CompilerOptsAuto(
+                macIncludeStdLib: ((node.value)[strings.macos]
+                    as Map?)?[strings.includeCStdLib] as bool,
+              ),
+              result: (node) => _compilerOpts.addAll(
+                  (node.value as CompilerOptsAuto).extractCompilerOpts()),
+            )),
+        HeterogeneousMapEntry(
+          key: strings.libraryImports,
+          valueConfigSpec: MapConfigSpec<String, Map<String, LibraryImport>>(
+            keyValueConfigSpecs: [
+              (keyRegexp: ".*", valueConfigSpec: StringConfigSpec()),
+            ],
+            customValidation: _libraryImportsPredefinedValidation,
+            transform: (node) => libraryImportsExtractor(node.value.cast()),
+          ),
+          defaultValue: (node) => <String, LibraryImport>{},
+          resultOrDefault: (node) =>
+              _libraryImports = (node.value) as Map<String, LibraryImport>,
+        ),
+        HeterogeneousMapEntry(
+            key: strings.functions,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+                ..._memberRenameProperties(),
+                HeterogeneousMapEntry(
+                  key: strings.symbolAddress,
+                  valueConfigSpec: _includeExcludeObject(),
+                  defaultValue: (node) => Includer.excludeByDefault(),
+                ),
+                HeterogeneousMapEntry(
+                  key: strings.exposeFunctionTypedefs,
+                  valueConfigSpec: _includeExcludeObject(),
+                  defaultValue: (node) => Includer.excludeByDefault(),
+                ),
+                HeterogeneousMapEntry(
+                  key: strings.leafFunctions,
+                  valueConfigSpec: _includeExcludeObject(),
+                  defaultValue: (node) => Includer.excludeByDefault(),
+                ),
+                HeterogeneousMapEntry(
+                  key: strings.varArgFunctions,
+                  valueConfigSpec: _functionVarArgsConfigSpec(),
+                  defaultValue: (node) => <String, List<RawVarArgFunction>>{},
+                  resultOrDefault: (node) {
+                    _varArgFunctions = makeVarArgFunctionsMapping(
+                        node.value as Map<String, List<RawVarArgFunction>>,
+                        _libraryImports);
+                  },
+                ),
+              ],
+              result: (node) {
+                _functionDecl = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+                _exposeFunctionTypedefs = (node.value
+                    as Map)[strings.exposeFunctionTypedefs] as Includer;
+                _leafFunctions =
+                    (node.value as Map)[strings.leafFunctions] as Includer;
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.structs,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+                ..._memberRenameProperties(),
+                _dependencyOnlyHeterogeneousMapKey(),
+                HeterogeneousMapEntry(
+                  key: strings.structPack,
+                  valueConfigSpec: MapConfigSpec(
+                    keyValueConfigSpecs: [
+                      (
+                        keyRegexp: '.*',
+                        valueConfigSpec: EnumConfigSpec(
+                          allowedValues: {'none', 1, 2, 4, 8, 16},
+                          transform: (node) =>
+                              node.value == 'none' ? null : node.value,
+                        ),
+                      )
+                    ],
+                    transform: (node) =>
+                        structPackingOverrideExtractor(node.value),
+                  ),
+                  defaultValue: (node) => StructPackingOverride(),
+                  resultOrDefault: (node) => _structPackingOverride =
+                      node.value as StructPackingOverride,
+                ),
+              ],
+              result: (node) {
+                _structDecl = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+                _structDependencies = (node.value
+                    as Map)[strings.dependencyOnly] as CompoundDependencies;
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.unions,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+                ..._memberRenameProperties(),
+                _dependencyOnlyHeterogeneousMapKey(),
+              ],
+              result: (node) {
+                _unionDecl = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+                _unionDependencies = (node.value as Map)[strings.dependencyOnly]
+                    as CompoundDependencies;
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.enums,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+                ..._memberRenameProperties(),
+              ],
+              result: (node) {
+                _enumClassDecl = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.unnamedEnums,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+              ],
+              result: (node) {
+                _unnamedEnumConstants = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.globals,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+                HeterogeneousMapEntry(
+                  key: strings.symbolAddress,
+                  valueConfigSpec: _includeExcludeObject(),
+                  defaultValue: (node) => Includer.excludeByDefault(),
+                )
+              ],
+              result: (node) {
+                _globals = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.macros,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+              ],
+              result: (node) {
+                _macroDecl = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.typedefs,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+              ],
+              result: (node) {
+                _typedefs = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.objcInterfaces,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                ..._includeExcludeProperties(),
+                ..._renameProperties(),
+                ..._memberRenameProperties(),
+                HeterogeneousMapEntry(
+                  key: strings.objcModule,
+                  valueConfigSpec: _objcInterfaceModuleObject(),
+                  defaultValue: (node) => ObjCModulePrefixer({}),
+                )
+              ],
+              result: (node) {
+                _objcInterfaces = declarationConfigExtractor(
+                    node.value as Map<dynamic, dynamic>);
+                _objcModulePrefixer = (node.value as Map)[strings.objcModule]
+                    as ObjCModulePrefixer;
+              },
+            )),
+        HeterogeneousMapEntry(
+            key: strings.import,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                HeterogeneousMapEntry(
+                  key: strings.symbolFilesImport,
+                  valueConfigSpec:
+                      ListConfigSpec<String, Map<String, ImportedType>>(
+                    childConfigSpec: StringConfigSpec(),
+                    transform: (node) => symbolFileImportExtractor(
+                        node.value, _libraryImports, filename, packageConfig),
+                  ),
+                  defaultValue: (node) => <String, ImportedType>{},
+                  resultOrDefault: (node) => _usrTypeMappings =
+                      node.value as Map<String, ImportedType>,
+                )
+              ],
+            )),
+        HeterogeneousMapEntry(
+            key: strings.typeMap,
+            valueConfigSpec: HeterogeneousMapConfigSpec(
+              entries: [
+                HeterogeneousMapEntry(
+                  key: strings.typeMapTypedefs,
+                  valueConfigSpec: _mappedTypeObject(),
+                  defaultValue: (node) => <String, List<String>>{},
+                ),
+                HeterogeneousMapEntry(
+                  key: strings.typeMapStructs,
+                  valueConfigSpec: _mappedTypeObject(),
+                  defaultValue: (node) => <String, List<String>>{},
+                ),
+                HeterogeneousMapEntry(
+                  key: strings.typeMapUnions,
+                  valueConfigSpec: _mappedTypeObject(),
+                  defaultValue: (node) => <String, List<String>>{},
+                ),
+                HeterogeneousMapEntry(
+                  key: strings.typeMapNativeTypes,
+                  valueConfigSpec: _mappedTypeObject(),
+                  defaultValue: (node) => <String, List<String>>{},
+                ),
+              ],
+              result: (node) {
+                final nodeValue = node.value as Map;
+                _typedefTypeMappings = makeImportTypeMapping(
+                  (nodeValue[strings.typeMapTypedefs])
+                      as Map<String, List<String>>,
+                  _libraryImports,
+                );
+                _structTypeMappings = makeImportTypeMapping(
+                  (nodeValue[strings.typeMapStructs])
+                      as Map<String, List<String>>,
+                  _libraryImports,
+                );
+                _unionTypeMappings = makeImportTypeMapping(
+                  (nodeValue[strings.typeMapUnions])
+                      as Map<String, List<String>>,
+                  _libraryImports,
+                );
+                _nativeTypeMappings = makeImportTypeMapping(
+                  (nodeValue[strings.typeMapNativeTypes])
+                      as Map<String, List<String>>,
+                  _libraryImports,
+                );
+              },
+            )),
+        HeterogeneousMapEntry(
+          key: strings.excludeAllByDefault,
+          valueConfigSpec: BoolConfigSpec(),
+          defaultValue: (node) => false,
+          resultOrDefault: (node) => _excludeAllByDefault = node.value as bool,
+        ),
+        HeterogeneousMapEntry(
+          key: strings.sort,
+          valueConfigSpec: BoolConfigSpec(),
+          defaultValue: (node) => false,
+          resultOrDefault: (node) => _sort = node.value as bool,
+        ),
+        HeterogeneousMapEntry(
+          key: strings.useSupportedTypedefs,
+          valueConfigSpec: BoolConfigSpec(),
+          defaultValue: (node) => true,
+          resultOrDefault: (node) => _useSupportedTypedefs = node.value as bool,
+        ),
+        HeterogeneousMapEntry(
+          key: strings.comments,
+          valueConfigSpec: _commentConfigSpec(),
+          defaultValue: (node) => CommentType.def(),
+          resultOrDefault: (node) => _commentType = node.value as CommentType,
+        ),
+        HeterogeneousMapEntry(
+          key: strings.name,
+          valueConfigSpec: _dartClassNameStringConfigSpec(),
+          defaultValue: (node) {
+            _logger.warning(
+                "Prefer adding Key '${node.pathString}' to your config.");
+            return 'NativeLibrary';
+          },
+          resultOrDefault: (node) => _wrapperName = node.value as String,
+        ),
+        HeterogeneousMapEntry(
+          key: strings.description,
+          valueConfigSpec: _nonEmptyStringConfigSpec(),
+          defaultValue: (node) {
+            _logger.warning(
+                "Prefer adding Key '${node.pathString}' to your config.");
+            return null;
+          },
+          resultOrDefault: (node) => _wrapperDocComment = node.value as String?,
+        ),
+        HeterogeneousMapEntry(
+            key: strings.preamble,
+            valueConfigSpec: StringConfigSpec(
+              result: (node) => _preamble = node.value as String?,
+            )),
+        HeterogeneousMapEntry(
+          key: strings.useDartHandle,
+          valueConfigSpec: BoolConfigSpec(),
+          defaultValue: (node) => true,
+          resultOrDefault: (node) => _useDartHandle = node.value as bool,
+        ),
+        HeterogeneousMapEntry(
+          key: strings.ffiNative,
+          valueConfigSpec: OneOfConfigSpec(
+            childConfigSpecs: [
+              EnumConfigSpec(allowedValues: {null}),
+              HeterogeneousMapConfigSpec(
+                entries: [
+                  HeterogeneousMapEntry(
+                    key: strings.ffiNativeAsset,
+                    valueConfigSpec: StringConfigSpec(),
+                    required: true,
+                  )
+                ],
+              )
+            ],
+            transform: (node) => ffiNativeExtractor(node.value),
+          ),
+          defaultValue: (node) => FfiNativeConfig(enabled: false),
+          resultOrDefault: (node) =>
+              _ffiNativeConfig = (node.value) as FfiNativeConfig,
+        ),
+      ],
+    );
   }
 
-  /// Extracts variables from Yaml according to given specs.
-  ///
-  /// Validation must be done beforehand, using [_checkConfigs].
-  void _extract(YamlMap map, Map<List<String>, Specification> specs) {
-    for (final key in specs.keys) {
-      final spec = specs[key];
-      if (checkKeyInYaml(key, map)) {
-        spec!.extractedResult(spec.extractor(getKeyValueFromYaml(key, map)));
-      } else {
-        spec!.extractedResult(spec.defaultValue?.call());
-      }
+  bool _libraryImportsPredefinedValidation(ConfigValue node) {
+    if (node.value is YamlMap) {
+      return (node.value as YamlMap).keys.where((key) {
+        if (strings.predefinedLibraryImports.containsKey(key)) {
+          _logger.severe(
+              '${node.pathString} -> $key should not collide with any predefined imports - ${strings.predefinedLibraryImports.keys}.');
+          return true;
+        }
+        return false;
+      }).isEmpty;
     }
+    return true;
   }
 
-  /// Returns map of various specifications avaialble for our tool.
-  ///
-  /// Key: Name, Value: [Specification]
-  Map<List<String>, Specification> _getSpecs() {
-    return <List<String>, Specification>{
-      [strings.llvmPath]: Specification<String>(
-        requirement: Requirement.no,
-        validator: llvmPathValidator,
-        extractor: llvmPathExtractor,
-        defaultValue: () => findDylibAtDefaultLocations(),
-        extractedResult: (dynamic result) {
-          _libclangDylib = result as String;
+  OneOfConfigSpec _commentConfigSpec() {
+    return OneOfConfigSpec(
+      childConfigSpecs: [
+        BoolConfigSpec(
+          transform: (node) =>
+              (node.value == true) ? CommentType.def() : CommentType.none(),
+        ),
+        HeterogeneousMapConfigSpec(
+          entries: [
+            HeterogeneousMapEntry(
+              key: strings.style,
+              valueConfigSpec: EnumConfigSpec(
+                allowedValues: {strings.doxygen, strings.any},
+                transform: (node) => node.value == strings.doxygen
+                    ? CommentStyle.doxygen
+                    : CommentStyle.any,
+              ),
+              defaultValue: (node) => CommentStyle.doxygen,
+            ),
+            HeterogeneousMapEntry(
+              key: strings.length,
+              valueConfigSpec: EnumConfigSpec(
+                allowedValues: {strings.brief, strings.full},
+                transform: (node) => node.value == strings.brief
+                    ? CommentLength.brief
+                    : CommentLength.full,
+              ),
+              defaultValue: (node) => CommentLength.full,
+            ),
+          ],
+          transform: (node) => CommentType(
+            (node.value)[strings.style] as CommentStyle,
+            (node.value)[strings.length] as CommentLength,
+          ),
+        ),
+      ],
+    );
+  }
+
+  MapConfigSpec _functionVarArgsConfigSpec() {
+    return MapConfigSpec(
+      keyValueConfigSpecs: [
+        (
+          keyRegexp: ".*",
+          valueConfigSpec: ListConfigSpec(
+            childConfigSpec: OneOfConfigSpec(
+              childConfigSpecs: [
+                ListConfigSpec(childConfigSpec: StringConfigSpec()),
+                HeterogeneousMapConfigSpec(
+                  entries: [
+                    HeterogeneousMapEntry(
+                      key: strings.types,
+                      valueConfigSpec: ListConfigSpec<String, List<String>>(
+                          childConfigSpec: StringConfigSpec()),
+                      required: true,
+                    ),
+                    HeterogeneousMapEntry(
+                      key: strings.postfix,
+                      valueConfigSpec: StringConfigSpec(),
+                    ),
+                  ],
+                )
+              ],
+            ),
+          )
+        )
+      ],
+      transform: (node) => varArgFunctionConfigExtractor(node.value),
+    );
+  }
+
+  HeterogeneousMapConfigSpec _outputFullConfigSpec() {
+    return HeterogeneousMapConfigSpec(
+      entries: [
+        HeterogeneousMapEntry(
+          key: strings.bindings,
+          valueConfigSpec: _filePathStringConfigSpec(),
+          required: true,
+        ),
+        HeterogeneousMapEntry(
+          key: strings.symbolFile,
+          valueConfigSpec: HeterogeneousMapConfigSpec(
+            entries: [
+              HeterogeneousMapEntry(
+                key: strings.output,
+                valueConfigSpec: _filePathStringConfigSpec(),
+                required: true,
+              ),
+              HeterogeneousMapEntry(
+                key: strings.importPath,
+                valueConfigSpec: StringConfigSpec(),
+                required: true,
+              ),
+            ],
+          ),
+        ),
+      ],
+    );
+  }
+
+  StringConfigSpec _filePathStringConfigSpec() {
+    return StringConfigSpec(
+      schemaDefName: 'filePath',
+      schemaDescription: "A file path",
+    );
+  }
+
+  StringConfigSpec _nonEmptyStringConfigSpec() {
+    return StringConfigSpec(
+      schemaDefName: 'nonEmptyString',
+      pattern: r'.+',
+    );
+  }
+
+  StringConfigSpec _dartClassNameStringConfigSpec() {
+    return StringConfigSpec(
+      schemaDefName: 'publicDartClass',
+      schemaDescription: "A public dart class name.",
+      pattern: r'^[a-zA-Z]+[_a-zA-Z0-9]*$',
+    );
+  }
+
+  List<HeterogeneousMapEntry> _includeExcludeProperties() {
+    return [
+      HeterogeneousMapEntry(
+        key: strings.include,
+        valueConfigSpec: _fullMatchOrRegexpList(),
+      ),
+      HeterogeneousMapEntry(
+        key: strings.exclude,
+        valueConfigSpec: _fullMatchOrRegexpList(),
+        defaultValue: (node) => <String>[],
+      ),
+    ];
+  }
+
+  ListConfigSpec<String, List<String>> _fullMatchOrRegexpList() {
+    return ListConfigSpec(
+      schemaDefName: "fullMatchOrRegexpList",
+      childConfigSpec: StringConfigSpec(),
+    );
+  }
+
+  List<HeterogeneousMapEntry> _renameProperties() {
+    return [
+      HeterogeneousMapEntry(
+        key: strings.rename,
+        valueConfigSpec: MapConfigSpec<String, dynamic>(
+          schemaDefName: "rename",
+          keyValueConfigSpecs: [
+            (keyRegexp: ".*", valueConfigSpec: StringConfigSpec()),
+          ],
+        ),
+      ),
+    ];
+  }
+
+  List<HeterogeneousMapEntry> _memberRenameProperties() {
+    return [
+      HeterogeneousMapEntry(
+        key: strings.memberRename,
+        valueConfigSpec: MapConfigSpec<Map<dynamic, String>,
+            Map<dynamic, Map<dynamic, String>>>(
+          schemaDefName: "memberRename",
+          keyValueConfigSpecs: [
+            (
+              keyRegexp: ".*",
+              valueConfigSpec: MapConfigSpec<String, Map<dynamic, String>>(
+                keyValueConfigSpecs: [
+                  (keyRegexp: ".*", valueConfigSpec: StringConfigSpec())
+                ],
+              ),
+            ),
+          ],
+        ),
+      ),
+    ];
+  }
+
+  HeterogeneousMapConfigSpec<List<String>, Includer> _includeExcludeObject() {
+    return HeterogeneousMapConfigSpec(
+      schemaDefName: "includeExclude",
+      entries: [
+        ..._includeExcludeProperties(),
+      ],
+      transform: (node) => extractIncluderFromYaml(node.value),
+    );
+  }
+
+  HeterogeneousMapEntry _dependencyOnlyHeterogeneousMapKey() {
+    return HeterogeneousMapEntry(
+      key: strings.dependencyOnly,
+      valueConfigSpec: EnumConfigSpec<String, CompoundDependencies>(
+        schemaDefName: "dependencyOnly",
+        allowedValues: {
+          strings.fullCompoundDependencies,
+          strings.opaqueCompoundDependencies,
         },
+        transform: (node) => node.value == strings.opaqueCompoundDependencies
+            ? CompoundDependencies.opaque
+            : CompoundDependencies.full,
       ),
-      [strings.output]: Specification<OutputConfig>(
-        requirement: Requirement.yes,
-        validator: outputValidator,
-        extractor: (dynamic value) =>
-            outputExtractor(value, filename, packageConfig),
-        extractedResult: (dynamic result) {
-          _output = (result as OutputConfig).output;
-          _symbolFile = result.symbolFile;
-        },
-      ),
-      [strings.language]: Specification<Language>(
-        requirement: Requirement.no,
-        validator: languageValidator,
-        extractor: languageExtractor,
-        defaultValue: () => Language.c,
-        extractedResult: (dynamic result) => _language = result as Language,
-      ),
-      [strings.headers]: Specification<Headers>(
-        requirement: Requirement.yes,
-        validator: headersValidator,
-        extractor: (dynamic value) => headersExtractor(value, filename),
-        extractedResult: (dynamic result) => _headers = result as Headers,
-      ),
-      [strings.compilerOpts]: Specification<List<String>>(
-        requirement: Requirement.no,
-        validator: compilerOptsValidator,
-        extractor: compilerOptsExtractor,
-        defaultValue: () => [],
-        extractedResult: (dynamic result) =>
-            _compilerOpts = result as List<String>,
-      ),
-      [strings.compilerOptsAuto]: Specification<CompilerOptsAuto>(
-          requirement: Requirement.no,
-          validator: compilerOptsAutoValidator,
-          extractor: compilerOptsAutoExtractor,
-          defaultValue: () => CompilerOptsAuto(),
-          extractedResult: (dynamic result) {
-            _compilerOpts
-                .addAll((result as CompilerOptsAuto).extractCompilerOpts());
-          }),
-      [strings.functions]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) {
-          _functionDecl = result as Declaration;
-        },
-      ),
-      [strings.structs]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) {
-          _structDecl = result as Declaration;
-        },
-      ),
-      [strings.unions]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) {
-          _unionDecl = result as Declaration;
-        },
-      ),
-      [strings.enums]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) {
-          _enumClassDecl = result as Declaration;
-        },
-      ),
-      [strings.unnamedEnums]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) =>
-            _unnamedEnumConstants = result as Declaration,
-      ),
-      [strings.globals]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) {
-          _globals = result as Declaration;
-        },
-      ),
-      [strings.macros]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) {
-          _macroDecl = result as Declaration;
-        },
-      ),
-      [strings.typedefs]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) {
-          _typedefs = result as Declaration;
-        },
-      ),
-      [strings.objcInterfaces]: Specification<Declaration>(
-        requirement: Requirement.no,
-        validator: declarationConfigValidator,
-        extractor: declarationConfigExtractor,
-        defaultValue: () => Declaration(),
-        extractedResult: (dynamic result) {
-          _objcInterfaces = result as Declaration;
-        },
-      ),
-      [strings.objcInterfaces, strings.objcModule]:
-          Specification<Map<String, String>>(
-        requirement: Requirement.no,
-        validator: stringStringMapValidator,
-        extractor: stringStringMapExtractor,
-        defaultValue: () => <String, String>{},
-        extractedResult: (dynamic result) => _objcModulePrefixer =
-            ObjCModulePrefixer(result as Map<String, String>),
-      ),
-      [strings.libraryImports]: Specification<Map<String, LibraryImport>>(
-        validator: libraryImportsValidator,
-        extractor: libraryImportsExtractor,
-        defaultValue: () => <String, LibraryImport>{},
-        extractedResult: (dynamic result) {
-          _libraryImports = result as Map<String, LibraryImport>;
-        },
-      ),
-      [strings.import, strings.symbolFilesImport]:
-          Specification<Map<String, ImportedType>>(
-        validator: symbolFileImportValidator,
-        extractor: (value) => symbolFileImportExtractor(
-            value, _libraryImports, filename, packageConfig),
-        defaultValue: () => <String, ImportedType>{},
-        extractedResult: (dynamic result) {
-          _usrTypeMappings = result as Map<String, ImportedType>;
-        },
-      ),
-      [strings.typeMap, strings.typeMapTypedefs]:
-          Specification<Map<String, List<String>>>(
-        validator: typeMapValidator,
-        extractor: typeMapExtractor,
-        defaultValue: () => <String, List<String>>{},
-        extractedResult: (dynamic result) {
-          _typedefTypeMappings = makeImportTypeMapping(
-              result as Map<String, List<String>>, _libraryImports);
-        },
-      ),
-      [strings.typeMap, strings.typeMapStructs]:
-          Specification<Map<String, List<String>>>(
-        validator: typeMapValidator,
-        extractor: typeMapExtractor,
-        defaultValue: () => <String, List<String>>{},
-        extractedResult: (dynamic result) {
-          _structTypeMappings = makeImportTypeMapping(
-              result as Map<String, List<String>>, _libraryImports);
-        },
-      ),
-      [strings.typeMap, strings.typeMapUnions]:
-          Specification<Map<String, List<String>>>(
-        validator: typeMapValidator,
-        extractor: typeMapExtractor,
-        defaultValue: () => <String, List<String>>{},
-        extractedResult: (dynamic result) {
-          _unionTypeMappings = makeImportTypeMapping(
-              result as Map<String, List<String>>, _libraryImports);
-        },
-      ),
-      [strings.typeMap, strings.typeMapNativeTypes]:
-          Specification<Map<String, List<String>>>(
-        validator: typeMapValidator,
-        extractor: typeMapExtractor,
-        defaultValue: () => <String, List<String>>{},
-        extractedResult: (dynamic result) {
-          _nativeTypeMappings = makeImportTypeMapping(
-              result as Map<String, List<String>>, _libraryImports);
-        },
-      ),
-      [strings.functions, strings.varArgFunctions]:
-          Specification<Map<String, List<RawVarArgFunction>>>(
-        requirement: Requirement.no,
-        validator: varArgFunctionConfigValidator,
-        extractor: varArgFunctionConfigExtractor,
-        defaultValue: () => <String, List<RawVarArgFunction>>{},
-        extractedResult: (dynamic result) {
-          _varArgFunctions = makeVarArgFunctionsMapping(
-              result as Map<String, List<RawVarArgFunction>>, _libraryImports);
-        },
-      ),
-      [strings.excludeAllByDefault]: Specification<bool>(
-        requirement: Requirement.no,
-        validator: booleanValidator,
-        extractor: booleanExtractor,
-        defaultValue: () => false,
-        extractedResult: (dynamic result) =>
-            _excludeAllByDefault = result as bool,
-      ),
-      [strings.sort]: Specification<bool>(
-        requirement: Requirement.no,
-        validator: booleanValidator,
-        extractor: booleanExtractor,
-        defaultValue: () => false,
-        extractedResult: (dynamic result) => _sort = result as bool,
-      ),
-      [strings.useSupportedTypedefs]: Specification<bool>(
-        requirement: Requirement.no,
-        validator: booleanValidator,
-        extractor: booleanExtractor,
-        defaultValue: () => true,
-        extractedResult: (dynamic result) =>
-            _useSupportedTypedefs = result as bool,
-      ),
-      [strings.comments]: Specification<CommentType>(
-        requirement: Requirement.no,
-        validator: commentValidator,
-        extractor: commentExtractor,
-        defaultValue: () => CommentType.def(),
-        extractedResult: (dynamic result) =>
-            _commentType = result as CommentType,
-      ),
-      [strings.structs, strings.dependencyOnly]:
-          Specification<CompoundDependencies>(
-        requirement: Requirement.no,
-        validator: dependencyOnlyValidator,
-        extractor: dependencyOnlyExtractor,
-        defaultValue: () => CompoundDependencies.full,
-        extractedResult: (dynamic result) =>
-            _structDependencies = result as CompoundDependencies,
-      ),
-      [strings.unions, strings.dependencyOnly]:
-          Specification<CompoundDependencies>(
-        requirement: Requirement.no,
-        validator: dependencyOnlyValidator,
-        extractor: dependencyOnlyExtractor,
-        defaultValue: () => CompoundDependencies.full,
-        extractedResult: (dynamic result) =>
-            _unionDependencies = result as CompoundDependencies,
-      ),
-      [strings.structs, strings.structPack]:
-          Specification<StructPackingOverride>(
-        requirement: Requirement.no,
-        validator: structPackingOverrideValidator,
-        extractor: structPackingOverrideExtractor,
-        defaultValue: () => StructPackingOverride(),
-        extractedResult: (dynamic result) =>
-            _structPackingOverride = result as StructPackingOverride,
-      ),
-      [strings.name]: Specification<String>(
-        requirement: Requirement.prefer,
-        validator: dartClassNameValidator,
-        extractor: stringExtractor,
-        defaultValue: () => 'NativeLibrary',
-        extractedResult: (dynamic result) => _wrapperName = result as String,
-      ),
-      [strings.description]: Specification<String?>(
-        requirement: Requirement.prefer,
-        validator: nonEmptyStringValidator,
-        extractor: stringExtractor,
-        defaultValue: () => null,
-        extractedResult: (dynamic result) =>
-            _wrapperDocComment = result as String?,
-      ),
-      [strings.preamble]: Specification<String?>(
-        requirement: Requirement.no,
-        validator: nonEmptyStringValidator,
-        extractor: stringExtractor,
-        extractedResult: (dynamic result) => _preamble = result as String?,
-      ),
-      [strings.useDartHandle]: Specification<bool>(
-        requirement: Requirement.no,
-        validator: booleanValidator,
-        extractor: booleanExtractor,
-        defaultValue: () => true,
-        extractedResult: (dynamic result) => _useDartHandle = result as bool,
-      ),
-      [strings.functions, strings.exposeFunctionTypedefs]:
-          Specification<Includer>(
-        requirement: Requirement.no,
-        validator: exposeFunctionTypeValidator,
-        extractor: exposeFunctionTypeExtractor,
-        defaultValue: () => Includer.excludeByDefault(),
-        extractedResult: (dynamic result) =>
-            _exposeFunctionTypedefs = result as Includer,
-      ),
-      [strings.functions, strings.leafFunctions]: Specification<Includer>(
-        requirement: Requirement.no,
-        validator: leafFunctionValidator,
-        extractor: leafFunctionExtractor,
-        defaultValue: () => Includer.excludeByDefault(),
-        extractedResult: (dynamic result) =>
-            _leafFunctions = result as Includer,
-      ),
-      [strings.ffiNative]: Specification<FfiNativeConfig>(
-        requirement: Requirement.no,
-        validator: ffiNativeValidator,
-        extractor: ffiNativeExtractor,
-        defaultValue: () => FfiNativeConfig(enabled: false),
-        extractedResult: (dynamic result) =>
-            _ffiNativeConfig = result as FfiNativeConfig,
-      )
-    };
+      defaultValue: (node) => CompoundDependencies.full,
+    );
+  }
+
+  MapConfigSpec _mappedTypeObject() {
+    return MapConfigSpec(
+      schemaDefName: "mappedTypes",
+      keyValueConfigSpecs: [
+        (
+          keyRegexp: ".*",
+          valueConfigSpec: HeterogeneousMapConfigSpec(entries: [
+            HeterogeneousMapEntry(
+                key: strings.lib, valueConfigSpec: StringConfigSpec()),
+            HeterogeneousMapEntry(
+                key: strings.cType, valueConfigSpec: StringConfigSpec()),
+            HeterogeneousMapEntry(
+                key: strings.dartType, valueConfigSpec: StringConfigSpec()),
+          ]),
+        )
+      ],
+      transform: (node) => typeMapExtractor(node.value),
+    );
+  }
+
+  MapConfigSpec _objcInterfaceModuleObject() {
+    return MapConfigSpec(
+      schemaDefName: "objcInterfaceModule",
+      keyValueConfigSpecs: [
+        (keyRegexp: ".*", valueConfigSpec: StringConfigSpec()),
+      ],
+      transform: (node) =>
+          ObjCModulePrefixer(node.value.cast<String, String>()),
+    );
   }
 }
diff --git a/pkgs/ffigen/lib/src/config_provider/config_spec.dart b/pkgs/ffigen/lib/src/config_provider/config_spec.dart
new file mode 100644
index 0000000..3ad3249
--- /dev/null
+++ b/pkgs/ffigen/lib/src/config_provider/config_spec.dart
@@ -0,0 +1,822 @@
+import 'package:logging/logging.dart';
+import 'package:yaml/yaml.dart';
+
+final _logger = Logger('ffigen.config_provider.config');
+
+/// Base class for all ConfigSpecs to extend.
+///
+/// [TE] - type input for [transform], [RE] - type input for [result].
+///
+/// Validation -
+///
+/// - [customValidation] is called after the ConfigSpec hierarchical validations
+/// are completed.
+///
+/// Extraction -
+///
+/// - The data is first validated, if invalid it throws a
+/// ConfigSpecExtractionError.
+/// - The extracted data from the child(s) is collected and the value is
+/// transformed via [transform] (if specified).
+/// - Finally the [result] closure is called (if specified).
+abstract class ConfigSpec<TE extends Object?, RE extends Object?> {
+  /// Used to generate and refer the reference definition generated in json
+  /// schema. Must be unique for a nested Schema.
+  String? schemaDefName;
+
+  /// Used to generate the description field in json schema.
+  String? schemaDescription;
+
+  /// Custom validation hook, called post validation if successful.
+  bool Function(ConfigValue node)? customValidation;
+
+  /// Used to transform the payload to another type before passing to parent
+  /// nodes and [result].
+  RE Function(ConfigValue<TE> node)? transform;
+
+  /// Called when final result is prepared via [_extractNode].
+  void Function(ConfigValue<RE> node)? result;
+  ConfigSpec({
+    required this.schemaDefName,
+    required this.schemaDescription,
+    required this.customValidation,
+    required this.transform,
+    required this.result,
+  });
+
+  bool _validateNode(ConfigValue o, {bool log = true});
+
+  ConfigValue<RE> _extractNode(ConfigValue o);
+
+  /// ConfigSpec objects should call [_getJsonRefOrSchemaNode] instead to get the
+  /// child json schema.
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs);
+
+  Map<String, dynamic> _getJsonRefOrSchemaNode(Map<String, dynamic> defs) {
+    if (schemaDefName == null) {
+      return _generateJsonSchemaNode(defs);
+    }
+    defs.putIfAbsent(schemaDefName!, () => _generateJsonSchemaNode(defs));
+    return {r"$ref": "#/\$defs/$schemaDefName"};
+  }
+
+  Map<String, dynamic> generateJsonSchema(String schemaId) {
+    final defs = <String, dynamic>{};
+    final schemaMap = _generateJsonSchemaNode(defs);
+    return {
+      r"$id": schemaId,
+      r"$comment":
+          "This file is generated. To regenerate run: dart tool/generate_json_schema.dart in github.com/dart-lang/ffigen",
+      r"$schema": "https://json-schema.org/draft/2020-12/schema",
+      ...schemaMap,
+      r"$defs": defs,
+    };
+  }
+
+  /// Run validation on an object [value].
+  bool validate(dynamic value) {
+    return _validateNode(ConfigValue(path: [], value: value));
+  }
+
+  /// Extract ConfigSpecNode from [value]. This will call the [transform] for all
+  /// underlying ConfigSpecs if valid.
+  /// Should ideally only be called if [validate] returns True. Throws
+  /// [ConfigSpecExtractionError] if any validation fails.
+  ConfigValue extract(dynamic value) {
+    return _extractNode(ConfigValue(path: [], value: value));
+  }
+}
+
+/// An individual value in a config for a specific [path] instantiated from a [ConfigSpec].
+///
+/// A config value contains both the [value] that users of the configuration would want
+/// to use, as well as the [rawValue] that was provided as input to the configuration.
+class ConfigValue<TE> {
+  /// The path to this node.
+  ///
+  /// E.g - ["path", "to", "arr", "[1]", "item"]
+  final List<String> path;
+
+  /// Get a string representation for path.
+  ///
+  /// E.g - "path -> to -> arr -> [1] -> item"
+  String get pathString => path.join(" -> ");
+
+  /// Contains the underlying node value after all transformations and
+  /// default values have been applied.
+  final TE value;
+
+  /// Contains the raw underlying node value. Would be null for fields populated
+  /// but default values
+  final Object? rawValue;
+
+  ConfigValue({
+    required this.path,
+    required this.value,
+    Object? rawValue,
+    bool nullRawValue = false,
+  }) : rawValue = nullRawValue ? null : (rawValue ?? value);
+
+  /// Copy object with a different value.
+  ConfigValue<T> withValue<T>(T value, Object? rawValue) {
+    return ConfigValue<T>(
+      path: path,
+      value: value,
+      rawValue: rawValue,
+      nullRawValue: rawValue == null,
+    );
+  }
+
+  /// Transforms this with a nullable [transform] or return itself
+  /// and calls the [result] callback
+  ConfigValue<RE> transformOrThis<RE extends Object?>(
+    RE Function(ConfigValue<TE> value)? transform,
+    void Function(ConfigValue<RE> node)? resultCallback,
+  ) {
+    ConfigValue<RE> returnValue;
+    if (transform != null) {
+      returnValue = this.withValue(transform.call(this), rawValue);
+    } else {
+      returnValue = this.withValue(this.value as RE, rawValue);
+    }
+    resultCallback?.call(returnValue);
+    return returnValue;
+  }
+
+  /// Returns true if [value] is of Type [T].
+  bool checkType<T>({bool log = true}) {
+    if (value is! T) {
+      if (log) {
+        _logger.severe(
+            "Expected value of key '$pathString' to be of type '$T' (Got ${value.runtimeType}).");
+      }
+      return false;
+    }
+    return true;
+  }
+}
+
+class ConfigSpecExtractionError extends Error {
+  final ConfigValue? item;
+  final String message;
+  ConfigSpecExtractionError(this.item, [this.message = "Invalid ConfigSpec"]);
+
+  @override
+  String toString() {
+    if (item != null) {
+      return "$runtimeType: $message @ ${item!.pathString}";
+    }
+    return "$runtimeType: $message";
+  }
+}
+
+class HeterogeneousMapEntry {
+  final String key;
+  final ConfigSpec valueConfigSpec;
+  final Object? Function(ConfigValue<void> o)? defaultValue;
+  void Function(ConfigValue<Object?> node)? resultOrDefault;
+  final bool required;
+
+  HeterogeneousMapEntry({
+    required this.key,
+    required this.valueConfigSpec,
+    this.defaultValue,
+    this.resultOrDefault,
+    this.required = false,
+  });
+}
+
+enum AdditionalProperties { Allow, Warn, Error }
+
+/// ConfigSpec for a Map which has a fixed set of known keys.
+///
+/// [CE] typecasts result from entries->{}->valueConfigSpec.
+///
+/// [RE] typecasts result returned by this node.
+class HeterogeneousMapConfigSpec<CE extends Object?, RE extends Object?>
+    extends ConfigSpec<Map<dynamic, CE>, RE> {
+  final List<HeterogeneousMapEntry> entries;
+  final Set<String> allKeys;
+  final Set<String> requiredKeys;
+  final AdditionalProperties additionalProperties;
+
+  HeterogeneousMapConfigSpec({
+    required this.entries,
+    super.schemaDefName,
+    super.schemaDescription,
+    super.customValidation,
+    super.transform,
+    super.result,
+    this.additionalProperties = AdditionalProperties.Warn,
+  })  : requiredKeys = {
+          for (final kv in entries.where((kv) => kv.required)) kv.key
+        },
+        allKeys = {for (final kv in entries) kv.key};
+
+  @override
+  bool _validateNode(ConfigValue o, {bool log = true}) {
+    if (!o.checkType<Map>(log: log)) {
+      return false;
+    }
+
+    var result = true;
+    final inputMap = (o.value as Map);
+
+    for (final requiredKey in requiredKeys) {
+      if (!inputMap.containsKey(requiredKey)) {
+        if (log) {
+          _logger.severe(
+              "Key '${[...o.path, requiredKey].join(' -> ')}' is required.");
+        }
+        result = false;
+      }
+    }
+
+    for (final entry in entries) {
+      final path = [...o.path, entry.key.toString()];
+      if (!inputMap.containsKey(entry.key)) {
+        continue;
+      }
+      final configSpecNode =
+          ConfigValue(path: path, value: inputMap[entry.key]);
+      if (!entry.valueConfigSpec._validateNode(configSpecNode, log: log)) {
+        result = false;
+        continue;
+      }
+    }
+
+    if (additionalProperties != AdditionalProperties.Allow) {
+      for (final key in inputMap.keys) {
+        if (!allKeys.contains(key)) {
+          if (log) {
+            _logger.severe("Unknown key - '${[...o.path, key].join(' -> ')}'.");
+          }
+          if (additionalProperties == AdditionalProperties.Error) {
+            result = false;
+          }
+        }
+      }
+    }
+
+    if (!result && customValidation != null) {
+      return customValidation!.call(o);
+    }
+    return result;
+  }
+
+  dynamic _getAllDefaults(ConfigValue o) {
+    final result = <dynamic, CE>{};
+    for (final entry in entries) {
+      final path = [...o.path, entry.key];
+      if (entry.defaultValue != null) {
+        result[entry.key] = entry.defaultValue!
+            .call(ConfigValue(path: path, value: null)) as CE;
+      } else if (entry.valueConfigSpec is HeterogeneousMapConfigSpec) {
+        final defaultValue =
+            (entry.valueConfigSpec as HeterogeneousMapConfigSpec)
+                ._getAllDefaults(ConfigValue(path: path, value: null));
+        if (defaultValue != null) {
+          result[entry.key] =
+              (entry.valueConfigSpec as HeterogeneousMapConfigSpec)
+                  ._getAllDefaults(ConfigValue(path: path, value: null)) as CE;
+        }
+      }
+      if (result.containsKey(entry.key) && entry.resultOrDefault != null) {
+        // Call resultOrDefault hook for HeterogeneousMapEntry.
+        entry.resultOrDefault!.call(ConfigValue(
+            path: path, value: result[entry.key], nullRawValue: true));
+      }
+    }
+    return result.isEmpty
+        ? null
+        : o
+            .withValue(result, null)
+            .transformOrThis(transform, this.result)
+            .value;
+  }
+
+  @override
+  ConfigValue<RE> _extractNode(ConfigValue o) {
+    if (!o.checkType<Map>(log: false)) {
+      throw ConfigSpecExtractionError(o);
+    }
+
+    final inputMap = (o.value as Map);
+    final childExtracts = <dynamic, CE>{};
+
+    for (final requiredKey in requiredKeys) {
+      if (!inputMap.containsKey(requiredKey)) {
+        throw ConfigSpecExtractionError(
+            null, "Invalid config spec, missing required key - $requiredKey.");
+      }
+    }
+
+    for (final entry in entries) {
+      final path = [...o.path, entry.key.toString()];
+      if (!inputMap.containsKey(entry.key)) {
+        // No value specified, fill in with default value instead.
+        if (entry.defaultValue != null) {
+          childExtracts[entry.key] = entry.defaultValue!
+              .call(ConfigValue(path: path, value: null)) as CE;
+        } else if (entry.valueConfigSpec is HeterogeneousMapConfigSpec) {
+          final defaultValue =
+              (entry.valueConfigSpec as HeterogeneousMapConfigSpec)
+                  ._getAllDefaults(ConfigValue(path: path, value: null));
+          if (defaultValue != null) {
+            childExtracts[entry.key] = (entry.valueConfigSpec
+                    as HeterogeneousMapConfigSpec)
+                ._getAllDefaults(ConfigValue(path: path, value: null)) as CE;
+          }
+        }
+      } else {
+        // Extract value from node.
+        final configSpecNode =
+            ConfigValue(path: path, value: inputMap[entry.key]);
+        if (!entry.valueConfigSpec._validateNode(configSpecNode, log: false)) {
+          throw ConfigSpecExtractionError(configSpecNode);
+        }
+        childExtracts[entry.key] =
+            entry.valueConfigSpec._extractNode(configSpecNode).value as CE;
+      }
+
+      if (childExtracts.containsKey(entry.key) &&
+          entry.resultOrDefault != null) {
+        // Call resultOrDefault hook for HeterogeneousMapEntry.
+        entry.resultOrDefault!.call(ConfigValue(
+            path: path, value: childExtracts[entry.key], nullRawValue: true));
+      }
+    }
+
+    if (additionalProperties == AdditionalProperties.Error) {
+      for (final key in inputMap.keys) {
+        if (!allKeys.contains(key)) {
+          throw ConfigSpecExtractionError(
+              o, "Invalid ConfigSpec: additional properties not allowed.");
+        }
+      }
+    }
+
+    return o
+        .withValue(childExtracts, o.rawValue)
+        .transformOrThis(transform, result);
+  }
+
+  @override
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs) {
+    return {
+      "type": "object",
+      if (additionalProperties != AdditionalProperties.Allow)
+        "additionalProperties": false,
+      if (schemaDescription != null) "description": schemaDescription!,
+      if (entries.isNotEmpty)
+        "properties": {
+          for (final kv in entries)
+            kv.key: kv.valueConfigSpec._getJsonRefOrSchemaNode(defs)
+        },
+      if (requiredKeys.isNotEmpty) "required": requiredKeys.toList(),
+    };
+  }
+}
+
+/// ConfigSpec for a Map that can have any number of keys.
+///
+/// [CE] typecasts result from keyValueConfigSpecs->{}->valueConfigSpec.
+///
+/// [RE] typecasts result returned by this node.
+class MapConfigSpec<CE extends Object?, RE extends Object?>
+    extends ConfigSpec<Map<dynamic, CE>, RE> {
+  /// Both [keyRegexp] - [valueConfigSpec] pair is used to match a set of
+  /// key-value input. Atleast one entry must match against an input for it
+  /// to be considered valid.
+  ///
+  /// Note: [keyRegexp] will be matched against key.toString()
+  final List<({String keyRegexp, ConfigSpec valueConfigSpec})>
+      keyValueConfigSpecs;
+
+  MapConfigSpec({
+    required this.keyValueConfigSpecs,
+    super.schemaDefName,
+    super.schemaDescription,
+    super.customValidation,
+    super.transform,
+    super.result,
+  });
+
+  @override
+  bool _validateNode(ConfigValue o, {bool log = true}) {
+    if (!o.checkType<Map>(log: log)) {
+      return false;
+    }
+
+    var result = true;
+    final inputMap = (o.value as Map);
+
+    for (final MapEntry(key: key, value: value) in inputMap.entries) {
+      final configSpecNode =
+          ConfigValue(path: [...o.path, key.toString()], value: value);
+      var keyValueMatch = false;
+
+      /// Running first time with no logs.
+      for (final (keyRegexp: keyRegexp, valueConfigSpec: valueConfigSpec)
+          in keyValueConfigSpecs) {
+        if (RegExp(keyRegexp, dotAll: true).hasMatch(key.toString()) &&
+            valueConfigSpec._validateNode(configSpecNode, log: false)) {
+          keyValueMatch = true;
+          break;
+        }
+      }
+      if (!keyValueMatch) {
+        result = false;
+        // No configSpec matched, running again to print logs this time.
+        if (log) {
+          _logger.severe(
+              "'${configSpecNode.pathString}' must match atleast one of the allowed key regex and configSpec.");
+          for (final (keyRegexp: keyRegexp, valueConfigSpec: valueConfigSpec)
+              in keyValueConfigSpecs) {
+            if (!RegExp(keyRegexp, dotAll: true).hasMatch(key.toString())) {
+              _logger.severe(
+                  "'${configSpecNode.pathString}' does not match regex - '$keyRegexp' (Input - $key)");
+              continue;
+            }
+            if (valueConfigSpec._validateNode(configSpecNode, log: log)) {
+              continue;
+            }
+          }
+        }
+      }
+    }
+
+    if (!result && customValidation != null) {
+      return customValidation!.call(o);
+    }
+    return result;
+  }
+
+  @override
+  ConfigValue<RE> _extractNode(ConfigValue o) {
+    if (!o.checkType<Map>(log: false)) {
+      throw ConfigSpecExtractionError(o);
+    }
+
+    final inputMap = (o.value as Map);
+    final childExtracts = <dynamic, CE>{};
+    for (final MapEntry(key: key, value: value) in inputMap.entries) {
+      final configSpecNode =
+          ConfigValue(path: [...o.path, key.toString()], value: value);
+      var keyValueMatch = false;
+      for (final (keyRegexp: keyRegexp, valueConfigSpec: valueConfigSpec)
+          in keyValueConfigSpecs) {
+        if (RegExp(keyRegexp, dotAll: true).hasMatch(key.toString()) &&
+            valueConfigSpec._validateNode(configSpecNode, log: false)) {
+          childExtracts[key] =
+              valueConfigSpec._extractNode(configSpecNode).value as CE;
+          keyValueMatch = true;
+          break;
+        }
+      }
+      if (!keyValueMatch) {
+        throw ConfigSpecExtractionError(configSpecNode);
+      }
+    }
+
+    return o
+        .withValue(childExtracts, o.rawValue)
+        .transformOrThis(transform, result);
+  }
+
+  @override
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs) {
+    return {
+      "type": "object",
+      if (schemaDescription != null) "description": schemaDescription!,
+      if (keyValueConfigSpecs.isNotEmpty)
+        "patternProperties": {
+          for (final (keyRegexp: keyRegexp, valueConfigSpec: valueConfigSpec)
+              in keyValueConfigSpecs)
+            keyRegexp: valueConfigSpec._getJsonRefOrSchemaNode(defs)
+        }
+    };
+  }
+}
+
+/// ConfigSpec for a List.
+///
+/// [CE] typecasts result from [childConfigSpec].
+///
+/// [RE] typecasts result returned by this node.
+class ListConfigSpec<CE extends Object?, RE extends Object?>
+    extends ConfigSpec<List<CE>, RE> {
+  final ConfigSpec childConfigSpec;
+
+  ListConfigSpec({
+    required this.childConfigSpec,
+    super.schemaDefName,
+    super.schemaDescription,
+    super.customValidation,
+    super.transform,
+    super.result,
+  });
+
+  @override
+  bool _validateNode(ConfigValue o, {bool log = true}) {
+    if (!o.checkType<YamlList>(log: log)) {
+      return false;
+    }
+    final inputList = (o.value as YamlList).cast<dynamic>();
+    var result = true;
+    for (final (i, input) in inputList.indexed) {
+      final configSpecNode =
+          ConfigValue(path: [...o.path, "[$i]"], value: input);
+      if (!childConfigSpec._validateNode(configSpecNode, log: log)) {
+        result = false;
+        continue;
+      }
+    }
+
+    if (!result && customValidation != null) {
+      return customValidation!.call(o);
+    }
+    return result;
+  }
+
+  @override
+  ConfigValue<RE> _extractNode(ConfigValue o) {
+    if (!o.checkType<YamlList>(log: false)) {
+      throw ConfigSpecExtractionError(o);
+    }
+    final inputList = (o.value as YamlList).cast<dynamic>();
+    final childExtracts = <CE>[];
+    for (final (i, input) in inputList.indexed) {
+      final configSpecNode =
+          ConfigValue(path: [...o.path, i.toString()], value: input);
+      if (!childConfigSpec._validateNode(configSpecNode, log: false)) {
+        throw ConfigSpecExtractionError(configSpecNode);
+      }
+      childExtracts
+          .add(childConfigSpec._extractNode(configSpecNode).value as CE);
+    }
+    return o
+        .withValue(childExtracts, o.rawValue)
+        .transformOrThis(transform, result);
+  }
+
+  @override
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs) {
+    return {
+      "type": "array",
+      if (schemaDescription != null) "description": schemaDescription!,
+      "items": childConfigSpec._getJsonRefOrSchemaNode(defs),
+    };
+  }
+}
+
+/// ConfigSpec for a String.
+///
+/// [RE] typecasts result returned by this node.
+class StringConfigSpec<RE extends Object?> extends ConfigSpec<String, RE> {
+  final String? pattern;
+  final RegExp? _regexp;
+
+  StringConfigSpec({
+    super.schemaDefName,
+    super.schemaDescription,
+    super.customValidation,
+    super.transform,
+    super.result,
+    this.pattern,
+  }) : _regexp = pattern == null ? null : RegExp(pattern, dotAll: true);
+
+  @override
+  bool _validateNode(ConfigValue o, {bool log = true}) {
+    if (!o.checkType<String>(log: log)) {
+      return false;
+    }
+    if (_regexp != null && !_regexp!.hasMatch(o.value as String)) {
+      if (log) {
+        _logger.severe(
+            "Expected value of key '${o.pathString}' to match pattern $pattern (Input - ${o.value}).");
+      }
+      return false;
+    }
+    if (customValidation != null) {
+      return customValidation!.call(o);
+    }
+    return true;
+  }
+
+  @override
+  ConfigValue<RE> _extractNode(ConfigValue o) {
+    if (!o.checkType<String>(log: false)) {
+      throw ConfigSpecExtractionError(o);
+    }
+    return o
+        .withValue(o.value as String, o.rawValue)
+        .transformOrThis(transform, result);
+  }
+
+  @override
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs) {
+    return {
+      "type": "string",
+      if (schemaDescription != null) "description": schemaDescription!,
+      if (pattern != null) "pattern": pattern,
+    };
+  }
+}
+
+/// ConfigSpec for an Int.
+///
+/// [RE] typecasts result returned by this node.
+class IntConfigSpec<RE extends Object?> extends ConfigSpec<int, RE> {
+  IntConfigSpec({
+    super.schemaDefName,
+    super.schemaDescription,
+    super.customValidation,
+    super.transform,
+    super.result,
+  });
+
+  @override
+  bool _validateNode(ConfigValue o, {bool log = true}) {
+    if (!o.checkType<int>(log: log)) {
+      return false;
+    }
+    if (customValidation != null) {
+      return customValidation!.call(o);
+    }
+    return true;
+  }
+
+  @override
+  ConfigValue<RE> _extractNode(ConfigValue o) {
+    if (!o.checkType<int>(log: false)) {
+      throw ConfigSpecExtractionError(o);
+    }
+    return o
+        .withValue(o.value as int, o.rawValue)
+        .transformOrThis(transform, result);
+  }
+
+  @override
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs) {
+    return {
+      "type": "integer",
+      if (schemaDescription != null) "description": schemaDescription!,
+    };
+  }
+}
+
+/// ConfigSpec for an object where only specific values are allowed.
+/// [CE] is the type for elements in [allowedValues].
+///
+/// [RE] typecasts result returned by this node.
+class EnumConfigSpec<CE extends Object?, RE extends Object?>
+    extends ConfigSpec<CE, RE> {
+  Set<CE> allowedValues;
+  EnumConfigSpec({
+    required this.allowedValues,
+    super.schemaDefName,
+    super.schemaDescription,
+    super.customValidation,
+    super.transform,
+    super.result,
+  });
+
+  @override
+  bool _validateNode(ConfigValue o, {bool log = true}) {
+    if (!allowedValues.contains(o.value)) {
+      if (log) {
+        _logger.severe(
+            "'${o.pathString}' must be one of the following - $allowedValues (Got ${o.value})");
+      }
+      return false;
+    }
+    if (customValidation != null) {
+      return customValidation!.call(o);
+    }
+    return true;
+  }
+
+  @override
+  ConfigValue<RE> _extractNode(ConfigValue o) {
+    if (!allowedValues.contains(o.value)) {
+      throw ConfigSpecExtractionError(o);
+    }
+    return o
+        .withValue(o.value as CE, o.rawValue)
+        .transformOrThis(transform, result);
+  }
+
+  @override
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs) {
+    return {
+      "enum": allowedValues.toList(),
+      if (schemaDescription != null) "description": schemaDescription!,
+    };
+  }
+}
+
+/// ConfigSpec for a bool.
+///
+/// [RE] typecasts result returned by this node.
+class BoolConfigSpec<RE> extends ConfigSpec<bool, RE> {
+  BoolConfigSpec({
+    super.schemaDefName,
+    super.schemaDescription,
+    super.customValidation,
+    super.transform,
+    super.result,
+  });
+
+  @override
+  bool _validateNode(ConfigValue o, {bool log = true}) {
+    if (!o.checkType<bool>(log: log)) {
+      return false;
+    }
+    if (customValidation != null) {
+      return customValidation!.call(o);
+    }
+    return true;
+  }
+
+  @override
+  ConfigValue<RE> _extractNode(ConfigValue o) {
+    if (!o.checkType<bool>(log: false)) {
+      throw ConfigSpecExtractionError(o);
+    }
+    return o
+        .withValue(o.value as bool, o.rawValue)
+        .transformOrThis(transform, result);
+  }
+
+  @override
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs) {
+    return {
+      "type": "boolean",
+      if (schemaDescription != null) "description": schemaDescription!,
+    };
+  }
+}
+
+/// ConfigSpec that requires atleast one underlying match.
+///
+/// [TE] typecasts the result returned by the the first valid [childConfigSpecs].
+///
+/// [RE] typecasts result returned by this node.
+class OneOfConfigSpec<TE extends Object?, RE extends Object?>
+    extends ConfigSpec<TE, RE> {
+  final List<ConfigSpec> childConfigSpecs;
+
+  OneOfConfigSpec({
+    required this.childConfigSpecs,
+    super.schemaDefName,
+    super.schemaDescription,
+    super.customValidation,
+    super.transform,
+    super.result,
+  });
+
+  @override
+  bool _validateNode(ConfigValue o, {bool log = true}) {
+    // Running first time with no logs.
+    for (final spec in childConfigSpecs) {
+      if (spec._validateNode(o, log: false)) {
+        if (customValidation != null) {
+          return customValidation!.call(o);
+        }
+        return true;
+      }
+    }
+    // No configSpec matched, running again to print logs this time.
+    if (log) {
+      _logger.severe(
+          "'${o.pathString}' must match atleast one of the allowed configSpec -");
+      for (final spec in childConfigSpecs) {
+        spec._validateNode(o, log: log);
+      }
+    }
+    return false;
+  }
+
+  @override
+  ConfigValue<RE> _extractNode(ConfigValue o) {
+    for (final spec in childConfigSpecs) {
+      if (spec._validateNode(o, log: false)) {
+        return o
+            .withValue(spec._extractNode(o).value as TE, o.rawValue)
+            .transformOrThis(transform, result);
+      }
+    }
+    throw ConfigSpecExtractionError(o);
+  }
+
+  @override
+  Map<String, dynamic> _generateJsonSchemaNode(Map<String, dynamic> defs) {
+    return {
+      if (schemaDescription != null) "description": schemaDescription!,
+      r"$oneOf": childConfigSpecs
+          .map((child) => child._getJsonRefOrSchemaNode(defs))
+          .toList(),
+    };
+  }
+}
diff --git a/pkgs/ffigen/lib/src/config_provider/config_types.dart b/pkgs/ffigen/lib/src/config_provider/config_types.dart
index 1eddfc9..75d5fca 100644
--- a/pkgs/ffigen/lib/src/config_provider/config_types.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config_types.dart
@@ -64,28 +64,6 @@
   }
 }
 
-/// Represents a single specification in configurations.
-///
-/// [E] is the return type of the extractedResult.
-class Specification<E> {
-  final bool Function(List<String> name, dynamic value) validator;
-  final E Function(dynamic map) extractor;
-  final E Function()? defaultValue;
-
-  final Requirement requirement;
-  final void Function(dynamic result) extractedResult;
-
-  Specification({
-    required this.extractedResult,
-    required this.validator,
-    required this.extractor,
-    this.defaultValue,
-    this.requirement = Requirement.no,
-  });
-}
-
-enum Requirement { yes, prefer, no }
-
 // Holds headers and filters for header.
 class Headers {
   /// Path to headers.
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
index 8bb3e2b..ff4e1b7 100644
--- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
+++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -39,115 +39,18 @@
       skipNormalization ? path : p.join(p.dirname(configFilename), path));
 }
 
-/// Checks if type of value is [T], logs an error if it's not.
-///
-/// [key] is printed as `'item1 -> item2 => item3'` in log message.
-bool checkType<T>(List<String> keys, dynamic value) {
-  if (value is! T) {
-    _logger.severe(
-        "Expected value of key '${keys.join(' -> ')}' to be of type '$T'.");
-    return false;
-  }
-  return true;
-}
-
-/// Checks if there are nested [key] in [map].
-bool checkKeyInYaml(List<String> key, YamlMap map) {
-  dynamic last = map;
-  for (final k in key) {
-    if (last is YamlMap) {
-      if (!last.containsKey(k)) return false;
-      last = last[k];
-    } else {
-      return false;
-    }
-  }
-  // The entry for the last key may be null.
-  return true;
-}
-
-/// Extracts value of nested [key] from [map].
-dynamic getKeyValueFromYaml(List<String> key, YamlMap map) {
-  if (checkKeyInYaml(key, map)) {
-    dynamic last = map;
-    for (final k in key) {
-      last = last[k];
-    }
-    return last;
-  }
-
-  return null;
-}
-
-/// Recursively checks the keys in [configKeyMap] from [allowedKeyList].
-void warnUnknownKeys(List<List<String>> allowedKeyList, YamlMap configKeyMap) {
-  final allowedKeyMap = <String, dynamic>{};
-  for (final specKeys in allowedKeyList) {
-    var item = allowedKeyMap;
-    for (final specSubKey in specKeys) {
-      item.putIfAbsent(specSubKey, () => <String, dynamic>{});
-      item = item[specSubKey] as Map<String, dynamic>;
-    }
-    // Add empty key to mark that any sub-keys of this key are allowed.
-    item[''] = <String, dynamic>{};
-  }
-  _warnUnknownKeysInMap(allowedKeyMap, configKeyMap, <dynamic>[]);
-}
-
-/// Recursive function to check a key set in a configKeyMap.
-void _warnUnknownKeysInMap(Map<String, dynamic> allowedKeyMap,
-    dynamic configKeyMap, List<dynamic> prev) {
-  if (allowedKeyMap.containsKey('') || configKeyMap is! YamlMap) {
-    return;
-  }
-  for (final key in configKeyMap.keys) {
-    if (allowedKeyMap.containsKey(key)) {
-      prev.add(key);
-      _warnUnknownKeysInMap(
-          allowedKeyMap[key] as Map<String, dynamic>, configKeyMap[key], prev);
-      prev.removeLast();
-    } else {
-      prev.add(key);
-      _logger.warning('Unknown key - ${prev.join(' -> ')}.');
-      prev.removeLast();
-    }
-  }
-}
-
-bool booleanExtractor(dynamic value) => value as bool;
-
-bool booleanValidator(List<String> name, dynamic value) =>
-    checkType<bool>(name, value);
-
-Map<String, LibraryImport> libraryImportsExtractor(dynamic yamlConfig) {
+Map<String, LibraryImport> libraryImportsExtractor(
+    Map<String, String>? typeMap) {
   final resultMap = <String, LibraryImport>{};
-  final typeMap = yamlConfig as YamlMap?;
   if (typeMap != null) {
     for (final typeName in typeMap.keys) {
-      resultMap[typeName as String] =
+      resultMap[typeName] =
           LibraryImport(typeName, typeMap[typeName] as String);
     }
   }
   return resultMap;
 }
 
-bool libraryImportsValidator(List<String> name, dynamic yamlConfig) {
-  if (!checkType<YamlMap>(name, yamlConfig)) {
-    return false;
-  }
-  for (final key in (yamlConfig as YamlMap).keys) {
-    if (!checkType<String>([...name, key as String], yamlConfig[key])) {
-      return false;
-    }
-    if (strings.predefinedLibraryImports.containsKey(key)) {
-      _logger.severe(
-          'library-import -> $key should not collide with any predefined imports - ${strings.predefinedLibraryImports.keys}.');
-      return false;
-    }
-  }
-  return true;
-}
-
 void loadImportedTypes(YamlMap fileConfig,
     Map<String, ImportedType> usrTypeMappings, LibraryImport libraryImport) {
   final symbols = fileConfig['symbols'] as YamlMap;
@@ -169,18 +72,14 @@
 }
 
 Map<String, ImportedType> symbolFileImportExtractor(
-    dynamic yamlConfig,
+    List<String> yamlConfig,
     Map<String, LibraryImport> libraryImports,
     String? configFileName,
     PackageConfig? packageConfig) {
   final resultMap = <String, ImportedType>{};
-  for (final item in (yamlConfig as YamlList)) {
+  for (final item in yamlConfig) {
     String symbolFilePath;
-    if (item is String) {
-      symbolFilePath = item;
-    } else {
-      symbolFilePath = item[strings.symbolFile] as String;
-    }
+    symbolFilePath = item;
     final symbolFile =
         loadSymbolFile(symbolFilePath, configFileName, packageConfig);
     final formatVersion = symbolFile[strings.formatVersion] as String;
@@ -210,44 +109,13 @@
   return resultMap;
 }
 
-bool symbolFileImportValidator(List<String> name, dynamic yamlConfig) {
-  if (!checkType<YamlList>(name, yamlConfig)) {
-    return false;
-  }
-  var result = true;
-  (yamlConfig as YamlList).asMap().forEach((idx, value) {
-    if (value is YamlMap) {
-      if (!value.keys.contains(strings.symbolFile)) {
-        result = false;
-        _logger
-            .severe('Key $name -> $idx -> ${strings.symbolFile} is required.');
-      }
-      for (final key in value.keys) {
-        if (key == strings.symbolFile) {
-          if (!checkType<String>(
-              [...name, idx.toString(), key as String], value[key])) {
-            result = false;
-          }
-        } else {
-          result = false;
-          _logger.severe('Unknown key : $name -> $idx -> $key.');
-        }
-      }
-    } else if (value is! String) {
-      result = false;
-      _logger.severe('Expected $name -> $idx should be a String or Map.');
-    }
-  });
-  return result;
-}
-
-Map<String, List<String>> typeMapExtractor(dynamic yamlConfig) {
+Map<String, List<String>> typeMapExtractor(Map<dynamic, dynamic>? yamlConfig) {
   // Key - type_name, Value - [lib, cType, dartType].
   final resultMap = <String, List<String>>{};
-  final typeMap = yamlConfig as YamlMap?;
+  final typeMap = yamlConfig;
   if (typeMap != null) {
     for (final typeName in typeMap.keys) {
-      final typeConfigItem = typeMap[typeName] as YamlMap;
+      final typeConfigItem = typeMap[typeName] as Map;
       resultMap[typeName as String] = [
         typeConfigItem[strings.lib] as String,
         typeConfigItem[strings.cType] as String,
@@ -258,57 +126,6 @@
   return resultMap;
 }
 
-bool typeMapValidator(List<String> name, dynamic yamlConfig) {
-  if (!checkType<YamlMap>(name, yamlConfig)) {
-    return false;
-  }
-  var result = true;
-  for (final key in (yamlConfig as YamlMap).keys) {
-    if (!checkType<YamlMap>([...name, key as String], yamlConfig[key])) {
-      return false;
-    }
-    final lib = (yamlConfig[key] as YamlMap).containsKey(strings.lib);
-    if (!lib) {
-      _logger.severe("Key '${strings.lib}' in $name -> $key is required.");
-      result = false;
-    }
-    final cType = (yamlConfig[key] as YamlMap).containsKey(strings.cType);
-    if (!cType) {
-      _logger.severe("Key '${strings.cType}' in $name -> $key is required.");
-      result = false;
-    }
-    final dartType = (yamlConfig[key] as YamlMap).containsKey(strings.dartType);
-    if (!dartType) {
-      _logger.severe("Key '${strings.dartType}' in $name -> $key is required.");
-      result = false;
-    }
-  }
-  return result;
-}
-
-Map<String, String> stringStringMapExtractor(dynamic yamlConfig) {
-  final resultMap = <String, String>{};
-  final inputMap = yamlConfig as YamlMap?;
-  if (inputMap != null) {
-    for (final key in inputMap.keys) {
-      resultMap[key as String] = inputMap[key] as String;
-    }
-  }
-  return resultMap;
-}
-
-bool stringStringMapValidator(List<String> name, dynamic yamlConfig) {
-  if (!checkType<YamlMap>(name, yamlConfig)) {
-    return false;
-  }
-  for (final key in (yamlConfig as YamlMap).keys) {
-    if (!checkType<String>([...name, key as String], yamlConfig[key])) {
-      return false;
-    }
-  }
-  return true;
-}
-
 Map<String, ImportedType> makeImportTypeMapping(
     Map<String, List<String>> rawTypeMappings,
     Map<String, LibraryImport> libraryImportsMap) {
@@ -440,77 +257,22 @@
   return list;
 }
 
-List<String> compilerOptsExtractor(dynamic value) {
-  if (value is String) {
-    return compilerOptsToList(value);
-  }
-
+List<String> compilerOptsExtractor(List<String> value) {
   final list = <String>[];
-  for (final el in (value as YamlList)) {
-    if (el is String) {
-      list.addAll(compilerOptsToList(el));
-    }
+  for (final el in (value)) {
+    list.addAll(compilerOptsToList(el));
   }
   return list;
 }
 
-bool compilerOptsValidator(List<String> name, dynamic value) {
-  if (value is String || value is YamlList) {
-    return true;
-  } else {
-    _logger.severe('Expected $name to be a String or List of String.');
-    return false;
-  }
-}
-
-CompilerOptsAuto compilerOptsAutoExtractor(dynamic value) {
-  return CompilerOptsAuto(
-    macIncludeStdLib: getKeyValueFromYaml(
-      [strings.macos, strings.includeCStdLib],
-      value as YamlMap,
-    ) as bool?,
-  );
-}
-
-bool compilerOptsAutoValidator(List<String> name, dynamic value) {
-  var result = true;
-
-  if (!checkType<YamlMap>(name, value)) {
-    return false;
-  }
-
-  for (final oskey in (value as YamlMap).keys) {
-    if (oskey == strings.macos) {
-      if (!checkType<YamlMap>([...name, oskey as String], value[oskey])) {
-        return false;
-      }
-
-      for (final inckey in (value[oskey] as YamlMap).keys) {
-        if (inckey == strings.includeCStdLib) {
-          if (!checkType<bool>(
-              [...name, oskey, inckey as String], value[oskey][inckey])) {
-            result = false;
-          }
-        } else {
-          _logger.severe("Unknown key '$inckey' in '$name -> $oskey.");
-          result = false;
-        }
-      }
-    } else {
-      _logger.severe("Unknown key '$oskey' in '$name'.");
-      result = false;
-    }
-  }
-  return result;
-}
-
-Headers headersExtractor(dynamic yamlConfig, String? configFilename) {
+Headers headersExtractor(
+    Map<dynamic, List<String>> yamlConfig, String? configFilename) {
   final entryPoints = <String>[];
   final includeGlobs = <quiver.Glob>[];
-  for (final key in (yamlConfig as YamlMap).keys) {
+  for (final key in yamlConfig.keys) {
     if (key == strings.entryPoints) {
-      for (final h in (yamlConfig[key] as YamlList)) {
-        final headerGlob = _normalizePath(h as String, configFilename);
+      for (final h in (yamlConfig[key]!)) {
+        final headerGlob = _normalizePath(h, configFilename);
         // Add file directly to header if it's not a Glob but a File.
         if (File(headerGlob).existsSync()) {
           final osSpecificPath = headerGlob;
@@ -528,8 +290,8 @@
       }
     }
     if (key == strings.includeDirectives) {
-      for (final h in (yamlConfig[key] as YamlList)) {
-        final headerGlob = h as String;
+      for (final h in yamlConfig[key]!) {
+        final headerGlob = h;
         final fixedGlob = _normalizePath(headerGlob, configFilename);
         includeGlobs.add(quiver.Glob(fixedGlob));
       }
@@ -543,28 +305,6 @@
   );
 }
 
-bool headersValidator(List<String> name, dynamic value) {
-  if (!checkType<YamlMap>(name, value)) {
-    return false;
-  }
-  if (!(value as YamlMap).containsKey(strings.entryPoints)) {
-    _logger.severe("Required '$name -> ${strings.entryPoints}'.");
-    return false;
-  } else {
-    for (final key in value.keys) {
-      if (key == strings.entryPoints || key == strings.includeDirectives) {
-        if (!checkType<YamlList>([...name, key as String], value[key])) {
-          return false;
-        }
-      } else {
-        _logger.severe("Unknown key '$key' in '$name'.");
-        return false;
-      }
-    }
-    return true;
-  }
-}
-
 /// Returns location of dynamic library by searching default locations. Logs
 /// error and throws an Exception if not found.
 String findDylibAtDefaultLocations() {
@@ -640,10 +380,9 @@
   }
 }
 
-String llvmPathExtractor(dynamic value) {
+String llvmPathExtractor(List<String> value) {
   // Extract libclang's dylib from user specified paths.
-  for (final path in (value as YamlList)) {
-    if (path is! String) continue;
+  for (final path in value) {
     final dylibPath =
         findLibclangDylib(p.join(path, strings.dynamicLibParentName));
     if (dylibPath != null) {
@@ -672,19 +411,12 @@
   }
 }
 
-bool llvmPathValidator(List<String> name, dynamic value) {
-  if (!checkType<YamlList>(name, value)) {
-    return false;
-  }
-  return true;
-}
-
 OutputConfig outputExtractor(
     dynamic value, String? configFilename, PackageConfig? packageConfig) {
   if (value is String) {
     return OutputConfig(_normalizePath(value, configFilename), null);
   }
-  value = value as YamlMap;
+  value = value as Map;
   return OutputConfig(
     _normalizePath((value)[strings.bindings] as String, configFilename),
     value.containsKey(strings.symbolFile)
@@ -694,36 +426,9 @@
   );
 }
 
-bool outputValidator(List<String> name, dynamic value) {
-  if (value is String) {
-    return true;
-  } else if (value is YamlMap) {
-    final keys = value.keys;
-    var result = true;
-    for (final key in keys) {
-      if (key == strings.bindings) {
-        if (!checkType<String>([...name, key as String], value[key])) {
-          result = false;
-        }
-      } else if (key == strings.symbolFile) {
-        result = symbolFileOutputValidator(
-            [...name, strings.symbolFile], value[key]);
-      } else {
-        result = false;
-        _logger.severe("Unknown key '$key' in '$name'.");
-      }
-    }
-    return result;
-  } else {
-    _logger.severe(
-        "Expected value of key '${name.join(' -> ')}' to be a String or Map.");
-    return false;
-  }
-}
-
 SymbolFile symbolFileOutputExtractor(
     dynamic value, String? configFilename, PackageConfig? packageConfig) {
-  value = value as YamlMap;
+  value = value as Map;
   var output = value[strings.output] as String;
   if (Uri.parse(output).scheme != "package") {
     _logger.warning(
@@ -740,71 +445,19 @@
   return SymbolFile(importPath, output);
 }
 
-bool symbolFileOutputValidator(List<String> name, dynamic value) {
-  if (!checkType<YamlMap>(name, value)) {
-    return false;
-  }
-  if (!(value as YamlMap).containsKey(strings.output)) {
-    _logger.severe("Required '$name -> ${strings.output}'.");
-    return false;
-  }
-  if (!(value).containsKey(strings.importPath)) {
-    _logger.severe("Required '$name -> ${strings.importPath}'.");
-    return false;
-  }
-  for (final key in value.keys) {
-    if (key == strings.output || key == strings.importPath) {
-      if (!checkType<String>([...name, key as String], value[key])) {
-        return false;
-      }
-    } else {
-      _logger.severe("Unknown key '$key' in '$name'.");
-      return false;
-    }
-  }
-  return true;
-}
-
-Language languageExtractor(dynamic value) {
-  if (value == strings.langC) {
-    return Language.c;
-  } else if (value == strings.langObjC) {
-    return Language.objc;
-  }
-  return Language.c;
-}
-
-bool languageValidator(List<String> name, dynamic value) {
-  if (value is String) {
-    if (value == strings.langC) {
-      return true;
-    }
-    if (value == strings.langObjC) {
-      _logger.severe('Objective C support is EXPERIMENTAL. The API may change '
-          'in a breaking way without notice.');
-      return true;
-    }
-    _logger.severe("'$name' must be one of the following - "
-        "{${strings.langC}, ${strings.langObjC}}");
-    return false;
-  }
-  _logger.severe("Expected value of key '$name' to be a String.");
-  return false;
-}
-
 /// Returns true if [str] is not a full name.
 ///
 /// E.g `abc` is a full name, `abc.*` is not.
 bool isFullDeclarationName(String str) =>
     quiver.matchesFull(RegExp('[a-zA-Z_0-9]*'), str);
 
-Includer _extractIncluderFromYaml(dynamic yamlMap) {
+Includer extractIncluderFromYaml(Map<dynamic, dynamic> yamlMap) {
   final includeMatchers = <RegExp>[],
       includeFull = <String>{},
       excludeMatchers = <RegExp>[],
       excludeFull = <String>{};
 
-  final include = (yamlMap[strings.include] as YamlList?)?.cast<String>();
+  final include = yamlMap[strings.include] as List<String>?;
   if (include != null) {
     if (include.isEmpty) {
       return Includer.excludeByDefault();
@@ -818,7 +471,7 @@
     }
   }
 
-  final exclude = (yamlMap[strings.exclude] as YamlList?)?.cast<String>();
+  final exclude = yamlMap[strings.exclude] as List<String>?;
   if (exclude != null) {
     for (final str in exclude) {
       if (isFullDeclarationName(str)) {
@@ -838,17 +491,17 @@
 }
 
 Map<String, List<RawVarArgFunction>> varArgFunctionConfigExtractor(
-    dynamic yamlMap) {
+    Map<dynamic, dynamic> yamlMap) {
   final result = <String, List<RawVarArgFunction>>{};
-  final configMap = (yamlMap as YamlMap);
+  final configMap = yamlMap;
   for (final key in configMap.keys) {
     final List<RawVarArgFunction> vafuncs = [];
-    for (final rawVaFunc in (configMap[key] as YamlList)) {
-      if (rawVaFunc is YamlList) {
+    for (final rawVaFunc in (configMap[key] as List)) {
+      if (rawVaFunc is List) {
         vafuncs.add(RawVarArgFunction(null, rawVaFunc.cast()));
-      } else if (rawVaFunc is YamlMap) {
+      } else if (rawVaFunc is Map) {
         vafuncs.add(RawVarArgFunction(rawVaFunc[strings.postfix] as String?,
-            (rawVaFunc[strings.types] as YamlList).cast()));
+            (rawVaFunc[strings.types] as List).cast()));
       } else {
         throw Exception("Unexpected type in variadic-argument config.");
       }
@@ -859,80 +512,21 @@
   return result;
 }
 
-bool varArgFunctionConfigValidator(List<String> name, dynamic value) {
-  if (!checkType<YamlMap>(name, value)) {
-    return false;
-  }
-  var result = true;
-  for (final key in (value as YamlMap).keys) {
-    final list = value[key as String];
-    if (!checkType<YamlList>([...name, key], list)) {
-      result = false;
-      continue;
-    }
-    (list as YamlList).asMap().forEach((idx, subList) {
-      if (subList is YamlMap) {
-        if (!subList.containsKey(strings.types)) {
-          result = false;
-          _logger.severe('Missing required key - ${[
-            ...name,
-            key,
-            idx.toString(),
-            strings.types
-          ].join(" -> ")}');
-        }
-        subList.forEach((subkey, subvalue) {
-          subkey = subkey as String;
-          if (subkey == strings.postfix) {
-            if (!checkType<String>(
-                [...name, key, idx.toString(), subkey], subvalue)) {
-              result = false;
-            }
-          } else if (subkey == strings.types) {
-            if (!checkType<YamlList>(
-                [...name, key, idx.toString(), subkey], subvalue)) {
-              result = false;
-            }
-          } else {
-            result = false;
-            _logger.severe('Unknown key - ${[
-              ...name,
-              key,
-              idx.toString(),
-              subkey
-            ].join(" -> ")}');
-          }
-        });
-      } else if (subList is! YamlList) {
-        result = false;
-        _logger.severe('Expected ${[
-          ...name,
-          key,
-          idx
-        ].join(" -> ")} to be a List or a Map.');
-      }
-    });
-  }
-  return result;
-}
-
-Declaration declarationConfigExtractor(dynamic yamlMap) {
+Declaration declarationConfigExtractor(Map<dynamic, dynamic> yamlMap) {
   final renamePatterns = <RegExpRenamer>[];
   final renameFull = <String, String>{};
   final memberRenamePatterns = <RegExpMemberRenamer>[];
   final memberRenamerFull = <String, Renamer>{};
 
-  final includer = _extractIncluderFromYaml(yamlMap);
+  final includer = extractIncluderFromYaml(yamlMap);
 
-  Includer? symbolIncluder;
-  if (yamlMap[strings.symbolAddress] != null) {
-    symbolIncluder = _extractIncluderFromYaml(yamlMap[strings.symbolAddress]);
-  }
+  final symbolIncluder = yamlMap[strings.symbolAddress] as Includer?;
 
-  final rename = (yamlMap[strings.rename] as YamlMap?)?.cast<String, String>();
+  final rename = yamlMap[strings.rename] as Map<dynamic, String>?;
 
   if (rename != null) {
-    for (final str in rename.keys) {
+    for (final key in rename.keys) {
+      final str = key.toString();
       if (isFullDeclarationName(str)) {
         renameFull[str] = rename[str]!;
       } else {
@@ -943,20 +537,22 @@
   }
 
   final memberRename =
-      (yamlMap[strings.memberRename] as YamlMap?)?.cast<String, YamlMap>();
+      yamlMap[strings.memberRename] as Map<dynamic, Map<dynamic, String>>?;
 
   if (memberRename != null) {
-    for (final decl in memberRename.keys) {
+    for (final key in memberRename.keys) {
+      final decl = key.toString();
       final renamePatterns = <RegExpRenamer>[];
       final renameFull = <String, String>{};
 
-      final memberRenameMap = memberRename[decl]!.cast<String, String>();
+      final memberRenameMap = memberRename[decl]!;
       for (final member in memberRenameMap.keys) {
-        if (isFullDeclarationName(member)) {
-          renameFull[member] = memberRenameMap[member]!;
+        final memberStr = member.toString();
+        if (isFullDeclarationName(memberStr)) {
+          renameFull[memberStr] = memberRenameMap[member]!;
         } else {
           renamePatterns.add(RegExpRenamer(
-              RegExp(member, dotAll: true), memberRenameMap[member]!));
+              RegExp(memberStr, dotAll: true), memberRenameMap[member]!));
         }
       }
       if (isFullDeclarationName(decl)) {
@@ -992,294 +588,20 @@
   );
 }
 
-bool declarationConfigValidator(List<String> name, dynamic value) {
-  var result = true;
-  if (value is YamlMap) {
-    for (final key in value.keys) {
-      if (key == strings.include || key == strings.exclude) {
-        if (!checkType<YamlList>([...name, key as String], value[key])) {
-          result = false;
-        }
-      } else if (key == strings.rename) {
-        if (!checkType<YamlMap>([...name, key as String], value[key])) {
-          result = false;
-        } else {
-          for (final subkey in (value[key] as YamlMap).keys) {
-            if (!checkType<String>(
-                [...name, key, subkey as String], value[key][subkey])) {
-              result = false;
-            }
-          }
-        }
-      } else if (key == strings.memberRename) {
-        if (!checkType<YamlMap>([...name, key as String], value[key])) {
-          result = false;
-        } else {
-          for (final declNameKey in (value[key] as YamlMap).keys) {
-            if (!checkType<YamlMap>([...name, key, declNameKey as String],
-                value[key][declNameKey])) {
-              result = false;
-            } else {
-              for (final memberNameKey
-                  in ((value[key] as YamlMap)[declNameKey] as YamlMap).keys) {
-                if (!checkType<String>([
-                  ...name,
-                  key,
-                  declNameKey,
-                  memberNameKey as String,
-                ], value[key][declNameKey][memberNameKey])) {
-                  result = false;
-                }
-              }
-            }
-          }
-        }
-      } else if (key == strings.symbolAddress) {
-        if (!checkType<YamlMap>([...name, key as String], value[key])) {
-          result = false;
-        } else {
-          for (final subkey in (value[key] as YamlMap).keys) {
-            if (subkey == strings.include || subkey == strings.exclude) {
-              if (!checkType<YamlList>(
-                  [...name, key, subkey as String], value[key][subkey])) {
-                result = false;
-              }
-            } else {
-              _logger.severe("Unknown key '$subkey' in '$name -> $key'.");
-              result = false;
-            }
-          }
-        }
-      }
-    }
-  } else {
-    _logger.severe("Expected value '$name' to be a Map.");
-    result = false;
-  }
-  return result;
-}
-
-Includer exposeFunctionTypeExtractor(dynamic value) =>
-    _extractIncluderFromYaml(value);
-
-bool exposeFunctionTypeValidator(List<String> name, dynamic value) {
-  var result = true;
-
-  if (!checkType<YamlMap>(name, value)) {
-    result = false;
-  } else {
-    final mp = value as YamlMap;
-    for (final key in mp.keys) {
-      if (key == strings.include || key == strings.exclude) {
-        if (!checkType<YamlList>([...name, key as String], value[key])) {
-          result = false;
-        }
-      } else {
-        _logger.severe("Unknown subkey '$key' in '$name'.");
-        result = false;
-      }
-    }
-  }
-
-  return result;
-}
-
-Includer leafFunctionExtractor(dynamic value) =>
-    _extractIncluderFromYaml(value);
-
-bool leafFunctionValidator(List<String> name, dynamic value) {
-  var result = true;
-
-  if (!checkType<YamlMap>(name, value)) {
-    result = false;
-  } else {
-    final mp = value as YamlMap;
-    for (final key in mp.keys) {
-      if (key == strings.include || key == strings.exclude) {
-        if (!checkType<YamlList>([...name, key as String], value[key])) {
-          result = false;
-        }
-      } else {
-        _logger.severe("Unknown subkey '$key' in '$name'.");
-        result = false;
-      }
-    }
-  }
-
-  return result;
-}
-
-SupportedNativeType nativeSupportedType(int value, {bool signed = true}) {
-  switch (value) {
-    case 1:
-      return signed ? SupportedNativeType.Int8 : SupportedNativeType.Uint8;
-    case 2:
-      return signed ? SupportedNativeType.Int16 : SupportedNativeType.Uint16;
-    case 4:
-      return signed ? SupportedNativeType.Int32 : SupportedNativeType.Uint32;
-    case 8:
-      return signed ? SupportedNativeType.Int64 : SupportedNativeType.Uint64;
-    default:
-      throw Exception(
-          'Unsupported value given to sizemap, Allowed values for sizes are: 1, 2, 4, 8');
-  }
-}
-
-String stringExtractor(dynamic value) => value as String;
-
-bool nonEmptyStringValidator(List<String> name, dynamic value) {
-  if (value is String && value.isNotEmpty) {
-    return true;
-  } else {
-    _logger.severe("Expected value of key '$name' to be a non-empty String.");
-    return false;
-  }
-}
-
-bool dartClassNameValidator(List<String> name, dynamic value) {
-  if (value is String &&
-      quiver.matchesFull(RegExp('[a-zA-Z]+[_a-zA-Z0-9]*'), value)) {
-    return true;
-  } else {
-    _logger.severe(
-        "Expected value of key '$name' to be a valid public class name.");
-    return false;
-  }
-}
-
-CommentType commentExtractor(dynamic value) {
-  if (value is bool) {
-    if (value) {
-      return CommentType.def();
-    } else {
-      return CommentType.none();
-    }
-  }
-  final ct = CommentType.def();
-  if (value is YamlMap) {
-    for (final key in value.keys) {
-      if (key == strings.style) {
-        if (value[key] == strings.any) {
-          ct.style = CommentStyle.any;
-        } else if (value[key] == strings.doxygen) {
-          ct.style = CommentStyle.doxygen;
-        }
-      } else if (key == strings.length) {
-        if (value[key] == strings.full) {
-          ct.length = CommentLength.full;
-        } else if (value[key] == strings.brief) {
-          ct.length = CommentLength.brief;
-        }
-      }
-    }
-  }
-  return ct;
-}
-
-bool commentValidator(List<String> name, dynamic value) {
-  if (value is bool) {
-    return true;
-  } else if (value is YamlMap) {
-    var result = true;
-    for (final key in value.keys) {
-      if (key == strings.style) {
-        if (value[key] is! String ||
-            !(value[key] == strings.doxygen || value[key] == strings.any)) {
-          _logger.severe(
-              "'$name'>'${strings.style}' must be one of the following - {${strings.doxygen}, ${strings.any}}");
-          result = false;
-        }
-      } else if (key == strings.length) {
-        if (value[key] is! String ||
-            !(value[key] == strings.brief || value[key] == strings.full)) {
-          _logger.severe(
-              "'$name'>'${strings.length}' must be one of the following - {${strings.brief}, ${strings.full}}");
-          result = false;
-        }
-      } else {
-        _logger.severe("Unknown key '$key' in '$name'.");
-        result = false;
-      }
-    }
-    return result;
-  } else {
-    _logger.severe("Expected value of key '$name' to be a bool or a Map.");
-    return false;
-  }
-}
-
-CompoundDependencies dependencyOnlyExtractor(dynamic value) {
-  var result = CompoundDependencies.full;
-  if (value == strings.opaqueCompoundDependencies) {
-    result = CompoundDependencies.opaque;
-  }
-  return result;
-}
-
-bool dependencyOnlyValidator(List<String> name, dynamic value) {
-  var result = true;
-  if (value is! String ||
-      !(value == strings.fullCompoundDependencies ||
-          value == strings.opaqueCompoundDependencies)) {
-    _logger.severe(
-        "'$name' must be one of the following - {${strings.fullCompoundDependencies}, ${strings.opaqueCompoundDependencies}}");
-    result = false;
-  }
-  return result;
-}
-
-StructPackingOverride structPackingOverrideExtractor(dynamic value) {
+StructPackingOverride structPackingOverrideExtractor(
+    Map<dynamic, dynamic> value) {
   final matcherMap = <RegExp, int?>{};
-  for (final key in (value as YamlMap).keys) {
+  for (final key in value.keys) {
     matcherMap[RegExp(key as String, dotAll: true)] =
         strings.packingValuesMap[value[key]];
   }
   return StructPackingOverride(matcherMap: matcherMap);
 }
 
-bool structPackingOverrideValidator(List<String> name, dynamic value) {
-  var result = true;
-
-  if (!checkType<YamlMap>([...name], value)) {
-    result = false;
-  } else {
-    for (final key in (value as YamlMap).keys) {
-      if (!(strings.packingValuesMap.keys.contains(value[key]))) {
-        _logger.severe(
-            "'$name -> $key' must be one of the following - ${strings.packingValuesMap.keys.toList()}");
-        result = false;
-      }
-    }
-  }
-
-  return result;
-}
-
 FfiNativeConfig ffiNativeExtractor(dynamic yamlConfig) {
-  final yamlMap = yamlConfig as YamlMap?;
+  final yamlMap = yamlConfig as Map?;
   return FfiNativeConfig(
     enabled: true,
     asset: yamlMap?[strings.ffiNativeAsset] as String?,
   );
 }
-
-bool ffiNativeValidator(List<String> name, dynamic yamlConfig) {
-  if (!checkType<YamlMap?>(name, yamlConfig)) {
-    return false;
-  }
-  if (yamlConfig == null) {
-    // Empty means no asset name.
-    return true;
-  }
-  for (final key in (yamlConfig as YamlMap).keys) {
-    if (!checkType<String>([...name, key as String], yamlConfig[key])) {
-      return false;
-    }
-    if (key != strings.ffiNativeAsset) {
-      _logger.severe("'$name -> $key' must be one of the following - ${[
-        strings.ffiNativeAsset
-      ]}");
-    }
-  }
-  return true;
-}
diff --git a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
index c7d6e2f..a2217b4 100644
--- a/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
+++ b/pkgs/ffigen/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -2593,7 +2593,9 @@
 /// The visitor should return one of the \c CXChildVisitResult values
 /// to direct clang_visitCursorChildren().
 typedef CXCursorVisitor = ffi.Pointer<
-    ffi.NativeFunction<ffi.Int32 Function(CXCursor, CXCursor, CXClientData)>>;
+    ffi.NativeFunction<
+        ffi.Int32 Function(
+            CXCursor cursor, CXCursor parent, CXClientData client_data)>>;
 
 /// Opaque pointer representing client data that will be passed through
 /// to various callbacks and visitors.
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart
index 245dd7c..7613289 100644
--- a/pkgs/ffigen/lib/src/strings.dart
+++ b/pkgs/ffigen/lib/src/strings.dart
@@ -277,3 +277,7 @@
   _tmpDir ??= Directory.systemTemp.createTempSync();
   return _tmpDir!.path;
 }
+
+const ffigenJsonSchemaIndent = '  ';
+const ffigenJsonSchemaId = "https://json.schemastore.org/ffigen";
+const ffigenJsonSchemaFileName = "ffigen.schema.json";
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml
index c8fad03..68a0862 100644
--- a/pkgs/ffigen/pubspec.yaml
+++ b/pkgs/ffigen/pubspec.yaml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: ffigen
-version: 8.0.2
+version: 9.0.0
 description: Generator for FFI bindings, using LibClang to parse C header files.
 repository: https://github.com/dart-lang/ffigen
 
@@ -24,10 +24,11 @@
   logging: ^1.0.0
   cli_util: ^0.4.0
   glob: ^2.0.0
-  file: ^6.0.0
+  file: ^7.0.0
   package_config: ^2.1.0
   yaml_edit: ^2.0.3
 
 dev_dependencies:
   lints: ^2.0.1
   test: ^1.16.2
+  json_schema: ^5.1.1
diff --git a/pkgs/ffigen/test/config_tests/json_schema_test.dart b/pkgs/ffigen/test/config_tests/json_schema_test.dart
new file mode 100644
index 0000000..44ea1e7
--- /dev/null
+++ b/pkgs/ffigen/test/config_tests/json_schema_test.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:ffigen/ffigen.dart';
+import 'package:ffigen/src/strings.dart' as strings;
+import 'package:file/local.dart';
+import 'package:glob/glob.dart';
+import 'package:json_schema/json_schema.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+
+late Library actual, expected;
+
+void main() {
+  group('json_schema_test', () {
+    final schema = Config.getsRootConfigSpec()
+        .generateJsonSchema(strings.ffigenJsonSchemaId);
+
+    test('Schema Changes', () {
+      final actualJsonSchema =
+          JsonEncoder.withIndent(strings.ffigenJsonSchemaIndent).convert(
+        Config.getsRootConfigSpec()
+            .generateJsonSchema(strings.ffigenJsonSchemaId),
+      );
+      final expectedJsonSchema = File(strings.ffigenJsonSchemaFileName)
+          .readAsStringSync()
+          .replaceAll('\r\n', '\n');
+      expect(actualJsonSchema, expectedJsonSchema);
+    });
+
+    final jsonSchema = JsonSchema.create(schema);
+    test('Valid json schema', () {
+      expect(jsonSchema, isNot(null));
+    });
+
+    // Find all ffigen config files in the repo.
+    final configYamlGlob = Glob("**config.yaml");
+    final configYamlFiles =
+        configYamlGlob.listFileSystemSync(const LocalFileSystem());
+    test('$configYamlGlob files not empty', () {
+      expect(configYamlFiles.isNotEmpty, true);
+    });
+
+    final sharedBindingsConfigYamlGlob =
+        Glob("example/shared_bindings/ffigen_configs/**.yaml");
+    final sharedBindingsConfigYamlFiles = sharedBindingsConfigYamlGlob
+        .listFileSystemSync(const LocalFileSystem());
+    test('$sharedBindingsConfigYamlGlob files not emty', () {
+      expect(sharedBindingsConfigYamlFiles.isNotEmpty, true);
+    });
+
+    final allConfigFiles = configYamlFiles + sharedBindingsConfigYamlFiles;
+
+    for (final fe in allConfigFiles) {
+      test('validate config file: ${fe.path}', () {
+        final yamlDoc = loadYaml(File(fe.absolute.path).readAsStringSync());
+        final validationResult = jsonSchema.validate(yamlDoc);
+        expect(validationResult.errors.isEmpty, true,
+            reason: "Schema Errors: ${validationResult.errors}");
+        expect(validationResult.warnings.isEmpty, true,
+            reason: "Schema Warnings: ${validationResult.errors}");
+      });
+    }
+
+    test('Bare minimal input', () {
+      expect(
+          jsonSchema
+              .validate({
+                "output": "abcd.dart",
+                "headers": {
+                  "entry-points": ["a.h"]
+                }
+              })
+              .errors
+              .isEmpty,
+          true);
+    });
+    test('Fail input', () {
+      expect(jsonSchema.validate(null).errors.isNotEmpty, true);
+      expect(jsonSchema.validate({"a": 1}).errors.isNotEmpty, true);
+      expect(
+          jsonSchema.validate({"output": "abcd.dart"}).errors.isNotEmpty, true);
+    });
+  });
+}
diff --git a/pkgs/ffigen/test/config_tests/unknown_keys_warn_test.dart b/pkgs/ffigen/test/config_tests/unknown_keys_warn_test.dart
index 3dd0451..6f80757 100644
--- a/pkgs/ffigen/test/config_tests/unknown_keys_warn_test.dart
+++ b/pkgs/ffigen/test/config_tests/unknown_keys_warn_test.dart
@@ -28,12 +28,6 @@
 ${strings.typeMap}:
   'warn-2': 'warn'
   'warn-3': 'warn'
-${strings.functions}:
-  'warn-4': 'skip'
-${strings.structs}:
-  'warn-5': 'skip'
-${strings.unions}:
-  'warn-6': 'skip'
         ''');
       logString = logArr.join("\n");
     });
@@ -42,10 +36,5 @@
       expect(logString.contains('warn-2'), true);
       expect(logString.contains('warn-3'), true);
     });
-    test('Do not warn for unknown keys in declarations.', () {
-      expect(logString.contains('warn-4'), false);
-      expect(logString.contains('warn-5'), false);
-      expect(logString.contains('warn-6'), false);
-    });
   });
 }
diff --git a/pkgs/ffigen/tool/generate_json_schema.dart b/pkgs/ffigen/tool/generate_json_schema.dart
new file mode 100644
index 0000000..7a5a030
--- /dev/null
+++ b/pkgs/ffigen/tool/generate_json_schema.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// ================== GENERATING JSON SCHEMA =====================
+//    cd to project's root, and run -
+//    dart generate_json_schema.dart
+// ===============================================================
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:ffigen/ffigen.dart';
+import 'package:ffigen/src/strings.dart' as strings;
+
+void main() async {
+  final actualJsonSchema =
+      JsonEncoder.withIndent(strings.ffigenJsonSchemaIndent).convert(
+    Config.getsRootConfigSpec().generateJsonSchema(strings.ffigenJsonSchemaId),
+  );
+
+  final file = File(strings.ffigenJsonSchemaFileName);
+  if (!await file.exists()) {
+    throw Exception("File '${file.absolute.path}' does not exist.");
+  }
+  await file.writeAsString(actualJsonSchema);
+
+  print("Generated json schema: ${file.absolute.path}");
+}
diff --git a/pkgs/ffigen/tool/libclang_config.yaml b/pkgs/ffigen/tool/libclang_config.yaml
index 6ac924f..f0f9b3b 100644
--- a/pkgs/ffigen/tool/libclang_config.yaml
+++ b/pkgs/ffigen/tool/libclang_config.yaml
@@ -9,6 +9,8 @@
 #    dart run ffigen --config tool/libclang_config.yaml
 # ===============================================================
 
+# yaml-language-server: $schema=../ffigen.schema.json
+
 name: Clang
 description: Holds bindings to LibClang.
 output: '../lib/src/header_parser/clang_bindings/clang_bindings.dart'