Skip to content

Commit

Permalink
Disables trait command and state validation until it can be corrected
Browse files Browse the repository at this point in the history
  • Loading branch information
i8beef committed Jan 4, 2021
1 parent 4536bf2 commit 37cddd2
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public class JsonSchemaExtensionsTests
[InlineData(TraitType.Modes, "currentModeSettings.test", GoogleType.String)]
[InlineData(TraitType.ColorSetting, "color.spectrumHsv.saturation", GoogleType.Numeric)]
[InlineData(TraitType.EnergyStorage, "capacityRemaining.[0].rawValue", GoogleType.Numeric)]
[InlineData(TraitType.SensorState, "currentSensorStateData.[0].currentSensorState", GoogleType.String)]
[InlineData(TraitType.SensorState, "currentSensorStateData.[0].rawValue", GoogleType.Numeric)]
public void GetGoogleTypeForFlattenedPathReturns(TraitType traitType, string target, GoogleType googleType)
{
// Arrange
Expand All @@ -40,7 +42,7 @@ public void ValidateFlattenedPathReturnsTrue(TraitType traitType, string target)
var schema = schemas.FirstOrDefault(x => x.Trait == traitType);

// Act
var result = schema.StateSchema.Validator.ValidateFlattenedPath(target);
var result = schema.StateSchema.Validator.FlattenedPathExists(target);

// Assert
Assert.True(result);
Expand All @@ -58,7 +60,7 @@ public void ValidateFlattenedPathReturnsFalse(TraitType traitType, string target
var schema = schemas.FirstOrDefault(x => x.Trait == traitType);

// Act
var result = schema.StateSchema.Validator.ValidateFlattenedPath(target);
var result = schema.StateSchema.Validator.FlattenedPathExists(target);

// Assert
Assert.False(result);
Expand Down
74 changes: 36 additions & 38 deletions src/HomeAutio.Mqtt.GoogleHome/Extensions/JsonSchemaExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using HomeAutio.Mqtt.GoogleHome.Models.State;

Expand All @@ -17,13 +18,18 @@ public static class JsonSchemaExtensions
/// <returns>The <see cref="GoogleType"/> for the specified path.</returns>
public static ICollection<object> GetEnumValuesForFlattenedPath(this NJsonSchema.JsonSchema schema, string flattenedPath)
{
var foundSchema = schema.GetByFlattenedPath(flattenedPath);
if (foundSchema != null && foundSchema.IsEnumeration)
var foundSchemas = schema.GetByFlattenedPath(flattenedPath);

var result = new List<object>();
foreach (var foundSchema in foundSchemas)
{
return foundSchema.Enumeration;
if (foundSchema.IsEnumeration)
{
result.AddRange(foundSchema.Enumeration);
}
}

return null;
return result.Any() ? result : null;
}

/// <summary>
Expand All @@ -34,13 +40,13 @@ public static ICollection<object> GetEnumValuesForFlattenedPath(this NJsonSchema
/// <returns>The <see cref="GoogleType"/> for the specified path.</returns>
public static GoogleType GetGoogleTypeForFlattenedPath(this NJsonSchema.JsonSchema schema, string flattenedPath)
{
var foundSchema = schema.GetByFlattenedPath(flattenedPath);
if (foundSchema == null)
var foundSchemas = schema.GetByFlattenedPath(flattenedPath);
if (!foundSchemas.Any())
{
return GoogleType.Unknown;
}

switch (foundSchema.Type)
switch (foundSchemas[0].Type)
{
case NJsonSchema.JsonObjectType.Integer:
case NJsonSchema.JsonObjectType.Number:
Expand All @@ -60,18 +66,18 @@ public static GoogleType GetGoogleTypeForFlattenedPath(this NJsonSchema.JsonSche
/// <param name="schema">JSON Schema.</param>
/// <param name="flattenedPath">Flattened state path.</param>
/// <returns><c>true</c> if path is valid for schema, otherwise <c>false</c>.</returns>
public static bool ValidateFlattenedPath(this NJsonSchema.JsonSchema schema, string flattenedPath)
public static bool FlattenedPathExists(this NJsonSchema.JsonSchema schema, string flattenedPath)
{
return schema.GetByFlattenedPath(flattenedPath) != null;
return schema.GetByFlattenedPath(flattenedPath).Any();
}

/// <summary>
/// Gets the schema item for the specified path for this schema.
/// </summary>
/// <param name="schema">JSON Schema.</param>
/// <param name="flattenedPath">Flattened state path.</param>
/// <returns>The <see cref="NJsonSchema.JsonSchema"/> for the specified path.</returns>
public static NJsonSchema.JsonSchema GetByFlattenedPath(this NJsonSchema.JsonSchema schema, string flattenedPath)
/// <returns>A list of matching <see cref="NJsonSchema.JsonSchema"/> for the specified path.</returns>
public static IList<NJsonSchema.JsonSchema> GetByFlattenedPath(this NJsonSchema.JsonSchema schema, string flattenedPath)
{
const string delimiter = ".";
var paths = flattenedPath?.Split(delimiter, 2);
Expand All @@ -84,24 +90,21 @@ public static NJsonSchema.JsonSchema GetByFlattenedPath(this NJsonSchema.JsonSch
case NJsonSchema.JsonObjectType.None:
// Treat unspecified types as possible subschemas
if (currentPathFragment == null)
return null;
return new List<NJsonSchema.JsonSchema>();

var objectResults = new List<NJsonSchema.JsonSchema>();
if (schema.Properties != null && schema.Properties.ContainsKey(currentPathFragment))
{
// Recursive traversal of objects
return GetByFlattenedPath(schema.Properties[currentPathFragment], remainingPathFragment);
objectResults.AddRange(GetByFlattenedPath(schema.Properties[currentPathFragment], remainingPathFragment));
}

if (schema.AnyOf != null)
{
foreach (var propertySchema in schema.AnyOf)
{
// Unwrap and validate each as a possibility
var anyOfResult = GetByFlattenedPath(propertySchema, flattenedPath);
if (anyOfResult != null)
{
return anyOfResult;
}
objectResults.AddRange(GetByFlattenedPath(propertySchema, flattenedPath));
}
}

Expand All @@ -110,51 +113,46 @@ public static NJsonSchema.JsonSchema GetByFlattenedPath(this NJsonSchema.JsonSch
foreach (var propertySchema in schema.OneOf)
{
// Unwrap and validate each as a possibility
var oneOfResult = GetByFlattenedPath(propertySchema, flattenedPath);
if (oneOfResult != null)
{
return oneOfResult;
}
objectResults.AddRange(GetByFlattenedPath(propertySchema, flattenedPath));
}
}

if (schema.AllowAdditionalProperties)
if (schema.AdditionalPropertiesSchema != null)
{
// Use the additional property schema, if available
return schema.AdditionalPropertiesSchema;
objectResults.AddRange(GetByFlattenedPath(schema.AdditionalPropertiesSchema, flattenedPath));
}

if (objectResults.Any())
return objectResults;

break;
case NJsonSchema.JsonObjectType.Array:
if (currentPathFragment == null)
return null;
return new List<NJsonSchema.JsonSchema>();

if (schema.Item != null)
{
if (Regex.IsMatch(currentPathFragment, @"^\[\d+\]$"))
{
// Unwrap array item
var itemResult = GetByFlattenedPath(schema.Item, remainingPathFragment);
if (itemResult != null)
{
return itemResult;
}
return GetByFlattenedPath(schema.Item, remainingPathFragment);
}
}
else
{
var arrayItemResult = new List<NJsonSchema.JsonSchema>();
foreach (var branch in schema.Items)
{
if (Regex.IsMatch(currentPathFragment, @"^\[\d+\]$"))
{
// Unwrap array item
var breanchItemResult = GetByFlattenedPath(branch, remainingPathFragment);
if (breanchItemResult != null)
{
return breanchItemResult;
}
arrayItemResult.AddRange(GetByFlattenedPath(branch, remainingPathFragment));
}
}

if (arrayItemResult.Any())
return arrayItemResult;
}

break;
Expand All @@ -163,10 +161,10 @@ public static NJsonSchema.JsonSchema GetByFlattenedPath(this NJsonSchema.JsonSch
case NJsonSchema.JsonObjectType.Boolean:
case NJsonSchema.JsonObjectType.String:
// Matching leaf node found
return schema;
return new List<NJsonSchema.JsonSchema> { schema };
}

return null;
return new List<NJsonSchema.JsonSchema>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ public Models.Response.ExecutionResponsePayload Handle(Models.Request.ExecuteInt
var googleState = trait.GetGoogleStateFlattened(_stateCache, traitSchema);

// Map incoming params to "fake" state changes to override existing state value
var replacedParams = execution.Params
.ToFlatDictionary()
.ToDictionary(kvp => CommandToStateKeyMapper.Map(kvp.Key), kvp => kvp.Value);
var replacedParams = execution.Params != null
? execution.Params.ToFlatDictionary().ToDictionary(kvp => CommandToStateKeyMapper.Map(kvp.Key), kvp => kvp.Value)
: new Dictionary<string, object>();

foreach (var state in googleState)
{
Expand All @@ -160,14 +160,14 @@ public Models.Response.ExecutionResponsePayload Handle(Models.Request.ExecuteInt
// Only add to state response if specified in the command result schema, or fallback state schema
if (commandSchema.ResultsValidator != null)
{
if (commandSchema.ResultsValidator.ValidateFlattenedPath(state.Key))
if (commandSchema.ResultsValidator.FlattenedPathExists(state.Key))
{
states.Add(state.Key, value);
}
}
else if (traitSchema.StateSchema != null)
{
if (traitSchema.StateSchema.Validator.ValidateFlattenedPath(state.Key))
if (traitSchema.StateSchema.Validator.FlattenedPathExists(state.Key))
{
states.Add(state.Key, value);
}
Expand Down
2 changes: 1 addition & 1 deletion src/HomeAutio.Mqtt.GoogleHome/Models/State/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public IDictionary<string, object> GetGoogleState(IDictionary<string, string> st
continue;

var validState = trait.GetGoogleStateFlattened(stateCache, schema)
.Where(kvp => schema.StateSchema.Validator.ValidateFlattenedPath(kvp.Key))
.Where(kvp => schema.StateSchema.Validator.FlattenedPathExists(kvp.Key))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

foreach (var googleState in validState.ToNestedDictionary())
Expand Down
79 changes: 38 additions & 41 deletions src/HomeAutio.Mqtt.GoogleHome/Validation/DeviceTraitValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,34 +35,34 @@ public static IEnumerable<string> Validate(DeviceTrait deviceTrait)
validationErrors.AddRange(attributeErrors.Select(x => $"Attributes: {x.Path}: {x.Kind}"));
}

// State validation
if (deviceTrait.State != null && traitSchema.StateSchema?.Validator != null)
{
var stateJson = JsonConvert.SerializeObject(GetFakeGoogleState(deviceTrait.State, traitSchema));
var stateErrors = traitSchema.StateSchema.Validator.Validate(stateJson);
//// State validation
////if (deviceTrait.State != null && traitSchema.StateSchema?.Validator != null)
////{
//// var stateJson = JsonConvert.SerializeObject(GetFakeGoogleState(deviceTrait.State, traitSchema));
//// var stateErrors = traitSchema.StateSchema.Validator.Validate(stateJson);

validationErrors.AddRange(stateErrors.Select(x => $"State: {x.Path}: {x.Kind}"));
}
//// validationErrors.AddRange(stateErrors.Select(x => $"State: {x.Path}: {x.Kind}"));
////}

// Command validations
var deviceCommands = deviceTrait.Commands.ToDictionary(
k => k.Key,
v => v.Value?.ToDictionary(
x => x.Key,
x => (object)x.Value).ToNestedDictionary());
//// Command validations
////var deviceCommands = deviceTrait.Commands.ToDictionary(
//// k => k.Key,
//// v => v.Value?.ToDictionary(
//// x => x.Key,
//// x => (object)x.Value).ToNestedDictionary());

foreach (var command in deviceCommands)
{
var commandType = command.Key.ToEnum<CommandType>();
if (command.Value != null && traitSchema.CommandSchemas.Any(x => x.Command == commandType))
{
var commandValidator = traitSchema.CommandSchemas.First(x => x.Command == commandType).Validator;
var commandJson = JsonConvert.SerializeObject(command.Value);
var commandErrors = commandValidator.Validate(commandJson);
////foreach (var command in deviceCommands)
////{
//// var commandType = command.Key.ToEnum<CommandType>();
//// if (command.Value != null && traitSchema.CommandSchemas.Any(x => x.Command == commandType))
//// {
//// var commandValidator = traitSchema.CommandSchemas.First(x => x.Command == commandType).Validator;
//// var commandJson = JsonConvert.SerializeObject(command.Value);
//// var commandErrors = commandValidator.Validate(commandJson);

validationErrors.AddRange(commandErrors.Select(x => $"Commands ({command.Key}): {x.Path}: {x.Kind}"));
}
}
//// validationErrors.AddRange(commandErrors.Select(x => $"Commands ({command.Key}): {x.Path}: {x.Kind}"));
//// }
////}
}

return validationErrors;
Expand All @@ -79,24 +79,21 @@ private static IDictionary<string, object> GetFakeGoogleState(IDictionary<string
var stateValues = new Dictionary<string, object>();
foreach (var state in stateConfigs)
{
if (state.Value.Topic != null)
var googleType = traitSchema.GetGoogleTypeForFlattenedPath(state.Key);
var enumValues = traitSchema.GetEnumValuesForFlattenedPath(state.Key);
var enumValue = enumValues?.FirstOrDefault()?.ToString();
switch (googleType)
{
var googleType = traitSchema.GetGoogleTypeForFlattenedPath(state.Key);
var enumValues = traitSchema.GetEnumValuesForFlattenedPath(state.Key);
var enumValue = enumValues?.FirstOrDefault()?.ToString();
switch (googleType)
{
case GoogleType.Bool:
stateValues.Add(state.Key, state.Value.MapValueToGoogle(enumValue ?? "true", googleType));
break;
case GoogleType.Numeric:
stateValues.Add(state.Key, state.Value.MapValueToGoogle(enumValue ?? "1", googleType));
break;
case GoogleType.String:
default:
stateValues.Add(state.Key, state.Value.MapValueToGoogle(enumValue ?? "default", googleType));
break;
}
case GoogleType.Bool:
stateValues.Add(state.Key, state.Value.MapValueToGoogle(enumValue ?? "true", googleType));
break;
case GoogleType.Numeric:
stateValues.Add(state.Key, state.Value.MapValueToGoogle(enumValue ?? "1", googleType));
break;
case GoogleType.String:
default:
stateValues.Add(state.Key, state.Value.MapValueToGoogle(enumValue ?? "default", googleType));
break;
}
}

Expand Down

0 comments on commit 37cddd2

Please sign in to comment.