/// <summary>
        /// Instantiates from embedded resources for specified trait type.
        /// </summary>
        /// <param name="traitType">Trait type.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>An instantiated <see cref="TraitSchema"/>.</returns>
        public static async Task <TraitSchema> ForTraitType(TraitType traitType, CancellationToken cancellationToken = default)
        {
            // Get resource paths
            var allTraitsResourceBase = "HomeAutio.Mqtt.GoogleHome.schema.traits";
            var traitId           = traitType.ToEnumString();
            var traitName         = traitId.Substring(traitId.LastIndexOf('.') + 1).ToLower();
            var traitResourceBase = $"{allTraitsResourceBase}.{traitName}";

            // Get resource names
            var assembly  = typeof(TraitType).Assembly;
            var resources = assembly.GetManifestResourceNames();

            // If no resources match, dont instantiate
            if (!resources.Any(x => x.StartsWith(traitResourceBase)))
            {
                return(null);
            }

            var traitSchema = new TraitSchema {
                Trait = traitType
            };

            // Attributes
            var attributeFile = $"{traitResourceBase}.{traitName}.attributes.schema.json";

            traitSchema.AttributeSchema = await AttributeSchema.FromJson(GetResourceString(attributeFile, resources), cancellationToken);

            // States
            var statesFile = $"{traitResourceBase}.{traitName}.states.schema.json";

            traitSchema.StateSchema = await StateSchema.FromJson(GetResourceString(statesFile, resources), cancellationToken);

            // Commands
            var commandParamFiles = resources.Where(x => x.StartsWith($"{traitResourceBase}") && x.EndsWith(".params.schema.json"));

            foreach (var commandParamFile in commandParamFiles)
            {
                var commandResourceBase = commandParamFile.Replace(".params.schema.json", string.Empty);
                var commandTypeName     = commandResourceBase.Replace($"{traitResourceBase}.", string.Empty);
                var commandResultsFile  = $"{commandResourceBase}.results.schema.json";
                var commandErrorFile    = $"{commandResourceBase}.errors.schema.json";

                var commandType = Enum.Parse <CommandType>(commandTypeName, true);

                var commandSchema = await CommandSchema.FromJson(
                    commandType,
                    GetResourceString(commandParamFile, resources),
                    GetResourceString(commandResultsFile, resources),
                    GetResourceString(commandErrorFile, resources),
                    cancellationToken);

                if (commandSchema != null)
                {
                    traitSchema.CommandSchemas.Add(commandSchema);
                }
            }

            return(traitSchema);
        }
        /// <summary>
        /// Instantiates from supplied JSON string.
        /// </summary>
        /// <param name="json">JSON to parse.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <returns>An instantiated <see cref="StateSchema"/>.</returns>
        public static async Task <StateSchema> FromJson(string json, CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(json))
            {
                return(null);
            }

            var stateValidator = await JsonSchema.FromJsonAsync(json, cancellationToken);

            var stateSchema = new StateSchema
            {
                Examples  = ExtractExamples(json),
                Json      = json,
                Validator = stateValidator
            };

            return(stateSchema);
        }