Browse Source

Json2 (#884)

* Json2

* Fix tests, more tests and a lot of compiler fixes.

* Fix all code.

* Trim excess

* Build fix.

* Fix errors

* Fix model validation.

* Throw helper.

* More throw helpers.

* Get rid of a useless property.
pull/885/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
5309c37422
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 50
      backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs
  2. 2
      backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs
  3. 2
      backend/extensions/Squidex.Extensions/Samples/Middleware/DoubleLinkedContentMiddleware.cs
  4. 31
      backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs
  5. 26
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs
  6. 6
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs
  7. 10
      backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs
  8. 30
      backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetMetadata.cs
  9. 16
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs
  10. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs
  11. 40
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
  12. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/FlatContentData.cs
  13. 17
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs
  14. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/ContentFieldDataConverter.cs
  15. 4
      backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs
  16. 9
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs
  17. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs
  18. 3
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs
  19. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs
  20. 21
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs
  21. 82
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  22. 28
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/StringFormatter.cs
  23. 10
      backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs
  24. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueExtensions.cs
  25. 50
      backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueFactory.cs
  26. 17
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  27. 48
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs
  28. 43
      backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  29. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleVariable.cs
  30. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetEntityScriptVars.cs
  31. 10
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs
  32. 70
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs
  33. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs
  34. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs
  35. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs
  36. 36
      backend/src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs
  37. 46
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/ContentFluidExtension.cs
  38. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/JsonArrayFluidValue.cs
  39. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs
  40. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs
  41. 111
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs
  42. 50
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs
  43. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs
  44. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs
  45. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs
  46. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs
  47. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringTextValidator.cs
  48. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueObjectValuesValidator.cs
  49. 7
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/LuceneQueryVisitor.cs
  50. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs
  51. 13
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs
  52. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateRole.cs
  53. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs
  54. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettings.cs
  55. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs
  56. 5
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs
  57. 6
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs
  58. 3
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs
  59. 11
      backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs
  60. 5
      backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsGrain.cs
  61. 63
      backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs
  62. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateJob.cs
  63. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs
  64. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs
  65. 3
      backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs
  66. 14
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  67. 7
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs
  68. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentUnionGraphType.cs
  69. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs
  70. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs
  71. 79
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs
  72. 4
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs
  73. 20
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs
  74. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonValueNode.cs
  75. 1
      backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedTypes.cs
  76. 5
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs
  77. 10
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs
  78. 2
      backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs
  79. 24
      backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Extensions.cs
  80. 4
      backend/src/Squidex.Domain.Apps.Entities/Q.cs
  81. 6
      backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs
  82. 5
      backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs
  83. 4
      backend/src/Squidex.Domain.Users/DefaultKeyStore.cs
  84. 3
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonReader.cs
  85. 6
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/DomainIdSerializer.cs
  86. 3
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs
  87. 3
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs
  88. 126
      backend/src/Squidex.Infrastructure/Collections/ListDictionary.KeyCollection.cs
  89. 126
      backend/src/Squidex.Infrastructure/Collections/ListDictionary.ValueCollection.cs
  90. 269
      backend/src/Squidex.Infrastructure/Collections/ListDictionary.cs
  91. 2
      backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs
  92. 2
      backend/src/Squidex.Infrastructure/Diagnostics/Diagnoser.cs
  93. 28
      backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs
  94. 4
      backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs
  95. 60
      backend/src/Squidex.Infrastructure/Guard.cs
  96. 4
      backend/src/Squidex.Infrastructure/Json/JsonException.cs
  97. 70
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/JsonValueConverter.cs
  98. 8
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/NewtonsoftJsonSerializer.cs
  99. 3
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/TypeConverterJsonConverter.cs
  100. 24
      backend/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs

50
backend/extensions/Squidex.Extensions/Actions/Kafka/KafkaProducer.cs

@ -173,23 +173,23 @@ namespace Squidex.Extensions.Actions.Kafka
avroProducer?.Dispose(); avroProducer?.Dispose();
} }
private static object GetValue(IJsonValue value, Schema schema) private static object GetValue(JsonValue value, Schema schema)
{ {
switch (value) switch (value.Type)
{ {
case JsonString s when IsTypeOrUnionWith(schema, Schema.Type.String): case JsonValueType.String when IsTypeOrUnionWith(schema, Schema.Type.String):
return s.Value; return value.AsString;
case JsonNumber n when IsTypeOrUnionWith(schema, Schema.Type.Long): case JsonValueType.Number when IsTypeOrUnionWith(schema, Schema.Type.Long):
return (long)n.Value; return (long)value.AsNumber;
case JsonNumber n when IsTypeOrUnionWith(schema, Schema.Type.Float): case JsonValueType.Number when IsTypeOrUnionWith(schema, Schema.Type.Float):
return (float)n.Value; return (float)value.AsNumber;
case JsonNumber n when IsTypeOrUnionWith(schema, Schema.Type.Int): case JsonValueType.Number when IsTypeOrUnionWith(schema, Schema.Type.Int):
return (int)n.Value; return (int)value.AsNumber;
case JsonNumber n when IsTypeOrUnionWith(schema, Schema.Type.Double): case JsonValueType.Number when IsTypeOrUnionWith(schema, Schema.Type.Double):
return n.Value; return value.AsNumber;
case JsonBoolean b when IsTypeOrUnionWith(schema, Schema.Type.Boolean): case JsonValueType.Boolean when IsTypeOrUnionWith(schema, Schema.Type.Boolean):
return b.Value; return value.AsBoolean;
case JsonObject o when IsTypeOrUnionWith(schema, Schema.Type.Map): case JsonValueType.Object when IsTypeOrUnionWith(schema, Schema.Type.Map):
{ {
var mapResult = new Dictionary<string, object>(); var mapResult = new Dictionary<string, object>();
@ -197,14 +197,14 @@ namespace Squidex.Extensions.Actions.Kafka
{ {
var map = (MapSchema)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Map); var map = (MapSchema)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Map);
foreach (var (key, childValue) in o) foreach (var (key, childValue) in value.AsObject)
{ {
mapResult.Add(key, GetValue(childValue, map?.ValueSchema)); mapResult.Add(key, GetValue(childValue, map?.ValueSchema));
} }
} }
else if (schema is MapSchema map) else if (schema is MapSchema map)
{ {
foreach (var (key, childValue) in o) foreach (var (key, childValue) in value.AsObject)
{ {
mapResult.Add(key, GetValue(childValue, map?.ValueSchema)); mapResult.Add(key, GetValue(childValue, map?.ValueSchema));
} }
@ -213,7 +213,7 @@ namespace Squidex.Extensions.Actions.Kafka
return mapResult; return mapResult;
} }
case JsonObject o when IsTypeOrUnionWith(schema, Schema.Type.Record): case JsonValueType.Object when IsTypeOrUnionWith(schema, Schema.Type.Record):
{ {
GenericRecord result = null; GenericRecord result = null;
@ -223,7 +223,7 @@ namespace Squidex.Extensions.Actions.Kafka
result = new GenericRecord(record); result = new GenericRecord(record);
foreach (var (key, childValue) in o) foreach (var (key, childValue) in value.AsObject)
{ {
if (record != null && record.TryGetField(key, out var field)) if (record != null && record.TryGetField(key, out var field))
{ {
@ -235,7 +235,7 @@ namespace Squidex.Extensions.Actions.Kafka
{ {
result = new GenericRecord(record); result = new GenericRecord(record);
foreach (var (key, childValue) in o) foreach (var (key, childValue) in value.AsObject)
{ {
if (record.TryGetField(key, out var field)) if (record.TryGetField(key, out var field))
{ {
@ -247,7 +247,7 @@ namespace Squidex.Extensions.Actions.Kafka
return result; return result;
} }
case JsonArray a when IsTypeOrUnionWith(schema, Schema.Type.Array): case JsonValueType.Array when IsTypeOrUnionWith(schema, Schema.Type.Array):
{ {
var result = new List<object>(); var result = new List<object>();
@ -255,14 +255,14 @@ namespace Squidex.Extensions.Actions.Kafka
{ {
var arraySchema = (ArraySchema)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Array); var arraySchema = (ArraySchema)union.Schemas.FirstOrDefault(x => x.Tag == Schema.Type.Array);
foreach (var item in a) foreach (var item in value.AsArray)
{ {
result.Add(GetValue(item, arraySchema?.ItemSchema)); result.Add(GetValue(item, arraySchema?.ItemSchema));
} }
} }
else if (schema is ArraySchema array) else if (schema is ArraySchema array)
{ {
foreach (var item in a) foreach (var item in value.AsArray)
{ {
result.Add(GetValue(item, array.ItemSchema)); result.Add(GetValue(item, array.ItemSchema));
} }

2
backend/extensions/Squidex.Extensions/Actions/Script/ScriptActionHandler.cs

@ -41,7 +41,7 @@ namespace Squidex.Extensions.Actions.Script
var result = await scriptEngine.ExecuteAsync(vars, job.Script, ct: ct); var result = await scriptEngine.ExecuteAsync(vars, job.Script, ct: ct);
return Result.Success(result?.ToString()); return Result.Success(result.ToString());
} }
} }

2
backend/extensions/Squidex.Extensions/Samples/Middleware/DoubleLinkedContentMiddleware.cs

@ -102,7 +102,7 @@ namespace Squidex.Extensions.Samples.Middleware
{ {
if (data != null && data.TryGetValue("reference", out ContentFieldData fieldData)) if (data != null && data.TryGetValue("reference", out ContentFieldData fieldData))
{ {
return fieldData.Values.OfType<JsonArray>().SelectMany(x => x).SingleOrDefault()?.ToString(); return fieldData.Values.OfType<JsonArray>().SelectMany(x => x).SingleOrDefault().ToString();
} }
return null; return null;

31
backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs

@ -65,7 +65,7 @@ namespace Squidex.Extensions.Validation
if (data.TryGetValue(field.Name, out var fieldValue)) if (data.TryGetValue(field.Name, out var fieldValue))
{ {
if (fieldValue.TryGetValue(InvariantPartitioning.Key, out var temp) && temp != null) if (fieldValue.TryGetValue(InvariantPartitioning.Key, out var temp) && temp != default)
{ {
value = temp; value = temp;
} }
@ -73,20 +73,27 @@ namespace Squidex.Extensions.Validation
switch (field.RawProperties) switch (field.RawProperties)
{ {
case BooleanFieldProperties when value is JsonBoolean boolean: case BooleanFieldProperties when value.Type == JsonValueType.Boolean:
return boolean.Value; return value.AsBoolean;
case BooleanFieldProperties when value is JsonNull: case BooleanFieldProperties when value.Type == JsonValueType.Null:
return ClrValue.Null; return ClrValue.Null;
case NumberFieldProperties when value is JsonNumber number: case NumberFieldProperties when value.Type == JsonValueType.Number:
return number.Value; return value.AsNumber;
case NumberFieldProperties when value is JsonNull: case NumberFieldProperties when value.Type == JsonValueType.Null:
return ClrValue.Null; return ClrValue.Null;
case StringFieldProperties when value is JsonString @string: case StringFieldProperties when value.Type == JsonValueType.String:
return @string.Value; return value.AsString;
case StringFieldProperties when value is JsonNull: case StringFieldProperties when value.Type == JsonValueType.Null:
return ClrValue.Null; return ClrValue.Null;
case ReferencesFieldProperties when value is JsonArray array && array.FirstOrDefault() is JsonString @string: case ReferencesFieldProperties when value.Type == JsonValueType.Array:
return @string.Value; var first = value.AsArray.FirstOrDefault();
if (first.Type == JsonValueType.String)
{
return first.AsString;
}
break;
} }
return null; return null;

26
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Json/RolesSurrogate.cs

@ -11,21 +11,21 @@ using Squidex.Infrastructure.Security;
namespace Squidex.Domain.Apps.Core.Apps.Json namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
public sealed class RolesSurrogate : Dictionary<string, IJsonValue>, ISurrogate<Roles> public sealed class RolesSurrogate : Dictionary<string, JsonValue>, ISurrogate<Roles>
{ {
public void FromSource(Roles source) public void FromSource(Roles source)
{ {
foreach (var customRole in source.Custom) foreach (var customRole in source.Custom)
{ {
var permissions = JsonValue.Array(); var permissions = new JsonArray();
foreach (var permission in customRole.Permissions) foreach (var permission in customRole.Permissions)
{ {
permissions.Add(JsonValue.Create(permission.Id)); permissions.Add(permission.Id);
} }
var role = var role =
JsonValue.Object() new JsonObject()
.Add("permissions", permissions) .Add("permissions", permissions)
.Add("properties", customRole.Properties); .Add("properties", customRole.Properties);
@ -44,26 +44,28 @@ namespace Squidex.Domain.Apps.Core.Apps.Json
{ {
var (key, value) = x; var (key, value) = x;
var properties = JsonValue.Object(); var properties = new JsonObject();
var permissions = PermissionSet.Empty; var permissions = PermissionSet.Empty;
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
var array = value.AsArray;
if (array.Count > 0) if (array.Count > 0)
{ {
permissions = new PermissionSet(array.OfType<JsonString>().Select(x => x.ToString())); permissions = new PermissionSet(array.Where(x => x.Type == JsonValueType.String).Select(x => x.AsString));
} }
} }
else if (value is JsonObject obj) else if (value.Type == JsonValueType.Object)
{ {
if (obj.TryGetValue("permissions", out array!) && array.Count > 0) if (value.TryGetValue(JsonValueType.Array, "permissions", out var array))
{ {
permissions = new PermissionSet(array.OfType<JsonString>().Select(x => x.ToString())); permissions = new PermissionSet(array.AsArray.Where(x => x.Type == JsonValueType.String).Select(x => x.AsString));
} }
if (!obj.TryGetValue<JsonObject>("properties", out properties)) if (value.TryGetValue(JsonValueType.Object, "properties", out var obj))
{ {
properties = JsonValue.Object(); properties = obj.AsObject;
} }
} }

6
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Role.cs

@ -37,10 +37,10 @@ namespace Squidex.Domain.Apps.Core.Apps
public string Name { get; } = Guard.NotNullOrEmpty(Name); public string Name { get; } = Guard.NotNullOrEmpty(Name);
public PermissionSet Permissions { get; } = Permissions ?? PermissionSet.Empty;
public JsonObject Properties { get; } = Properties ?? new JsonObject(); public JsonObject Properties { get; } = Properties ?? new JsonObject();
public PermissionSet Permissions { get; } = Permissions ?? PermissionSet.Empty;
public bool IsDefault public bool IsDefault
{ {
get => Roles.IsDefault(this); get => Roles.IsDefault(this);
@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.Apps
public static Role WithPermissions(string name, params string[] permissions) public static Role WithPermissions(string name, params string[] permissions)
{ {
return new Role(name, new PermissionSet(permissions), JsonValue.Object()); return new Role(name, new PermissionSet(permissions), new JsonObject());
} }
public static Role WithProperties(string name, JsonObject properties) public static Role WithProperties(string name, JsonObject properties)

10
backend/src/Squidex.Domain.Apps.Core.Model/Apps/Roles.cs

@ -25,13 +25,13 @@ namespace Squidex.Domain.Apps.Core.Apps
new Role(Role.Owner, new Role(Role.Owner,
new PermissionSet( new PermissionSet(
WithoutPrefix(Permissions.App)), WithoutPrefix(Permissions.App)),
JsonValue.Object()), new JsonObject()),
[Role.Reader] = [Role.Reader] =
new Role(Role.Reader, new Role(Role.Reader,
new PermissionSet( new PermissionSet(
WithoutPrefix(Permissions.AppAssetsRead), WithoutPrefix(Permissions.AppAssetsRead),
WithoutPrefix(Permissions.AppContentsRead)), WithoutPrefix(Permissions.AppContentsRead)),
JsonValue.Object() new JsonObject()
.Add("ui.api.hide", true)), .Add("ui.api.hide", true)),
[Role.Editor] = [Role.Editor] =
new Role(Role.Editor, new Role(Role.Editor,
@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Apps
WithoutPrefix(Permissions.AppContents), WithoutPrefix(Permissions.AppContents),
WithoutPrefix(Permissions.AppRolesRead), WithoutPrefix(Permissions.AppRolesRead),
WithoutPrefix(Permissions.AppWorkflowsRead)), WithoutPrefix(Permissions.AppWorkflowsRead)),
JsonValue.Object() new JsonObject()
.Add("ui.api.hide", true)), .Add("ui.api.hide", true)),
[Role.Developer] = [Role.Developer] =
new Role(Role.Developer, new Role(Role.Developer,
@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Apps
WithoutPrefix(Permissions.AppRules), WithoutPrefix(Permissions.AppRules),
WithoutPrefix(Permissions.AppSchemas), WithoutPrefix(Permissions.AppSchemas),
WithoutPrefix(Permissions.AppWorkflows)), WithoutPrefix(Permissions.AppWorkflows)),
JsonValue.Object()) new JsonObject())
}; };
public static readonly Roles Empty = new Roles(new ReadonlyDictionary<string, Role>()); public static readonly Roles Empty = new Roles(new ReadonlyDictionary<string, Role>());
@ -105,7 +105,7 @@ namespace Squidex.Domain.Apps.Core.Apps
return this; return this;
} }
var newRole = new Role(name); var newRole = new Role(name, null, new JsonObject());
if (!inner.TryAdd(name, newRole, out var updated)) if (!inner.TryAdd(name, newRole, out var updated))
{ {

30
backend/src/Squidex.Domain.Apps.Core.Model/Assets/AssetMetadata.cs

@ -10,7 +10,7 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Assets namespace Squidex.Domain.Apps.Core.Assets
{ {
public sealed class AssetMetadata : Dictionary<string, IJsonValue> public sealed class AssetMetadata : Dictionary<string, JsonValue>
{ {
private static readonly char[] PathSeparators = { '.', '[', ']' }; private static readonly char[] PathSeparators = { '.', '[', ']' };
@ -23,42 +23,42 @@ namespace Squidex.Domain.Apps.Core.Assets
public AssetMetadata SetFocusX(float value) public AssetMetadata SetFocusX(float value)
{ {
this[FocusX] = JsonValue.Create(value); this[FocusX] = (double)value;
return this; return this;
} }
public AssetMetadata SetFocusY(float value) public AssetMetadata SetFocusY(float value)
{ {
this[FocusY] = JsonValue.Create(value); this[FocusY] = (double)value;
return this; return this;
} }
public AssetMetadata SetPixelWidth(int value) public AssetMetadata SetPixelWidth(int value)
{ {
this[PixelWidth] = JsonValue.Create(value); this[PixelWidth] = (double)value;
return this; return this;
} }
public AssetMetadata SetPixelHeight(int value) public AssetMetadata SetPixelHeight(int value)
{ {
this[PixelHeight] = JsonValue.Create(value); this[PixelHeight] = (double)value;
return this; return this;
} }
public AssetMetadata SetVideoWidth(int value) public AssetMetadata SetVideoWidth(int value)
{ {
this[VideoWidth] = JsonValue.Create(value); this[VideoWidth] = (double)value;
return this; return this;
} }
public AssetMetadata SetVideoHeight(int value) public AssetMetadata SetVideoHeight(int value)
{ {
this[VideoHeight] = JsonValue.Create(value); this[VideoHeight] = (double)value;
return this; return this;
} }
@ -95,9 +95,9 @@ namespace Squidex.Domain.Apps.Core.Assets
public int? GetIn32(string name) public int? GetIn32(string name)
{ {
if (TryGetValue(name, out var n) && n is JsonNumber number) if (TryGetValue(name, out var n) && n.Type == JsonValueType.Number)
{ {
return (int)number.Value; return (int)n.AsNumber;
} }
return null; return null;
@ -105,9 +105,9 @@ namespace Squidex.Domain.Apps.Core.Assets
public float? GetSingle(string name) public float? GetSingle(string name)
{ {
if (TryGetValue(name, out var n) && n is JsonNumber number) if (TryGetValue(name, out var n) && n.Type == JsonValueType.Number)
{ {
return (float)number.Value; return (float)n.AsNumber;
} }
return null; return null;
@ -115,9 +115,9 @@ namespace Squidex.Domain.Apps.Core.Assets
public bool TryGetNumber(string name, out double result) public bool TryGetNumber(string name, out double result)
{ {
if (TryGetValue(name, out var v) && v is JsonNumber n) if (TryGetValue(name, out var n) && n.Type == JsonValueType.Number)
{ {
result = n.Value; result = n.AsNumber;
return true; return true;
} }
@ -129,9 +129,9 @@ namespace Squidex.Domain.Apps.Core.Assets
public bool TryGetString(string name, [MaybeNullWhen(false)] out string result) public bool TryGetString(string name, [MaybeNullWhen(false)] out string result)
{ {
if (TryGetValue(name, out var v) && v is JsonString s) if (TryGetValue(name, out var s) && s.Type == JsonValueType.String)
{ {
result = s.Value; result = s.AsString;
return true; return true;
} }

16
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Component.cs

@ -20,30 +20,32 @@ namespace Squidex.Domain.Apps.Core.Contents
public string Type { get; } = Guard.NotNullOrEmpty(Type); public string Type { get; } = Guard.NotNullOrEmpty(Type);
public JsonObject Data { get; } = Guard.NotNull(Data);
public Schema Schema { get; } = Guard.NotNull(Schema); public Schema Schema { get; } = Guard.NotNull(Schema);
public static bool IsValid(IJsonValue? value, [MaybeNullWhen(false)] out string discriminator) public JsonObject Data { get; } = Guard.NotNull(Data);
public static bool IsValid(JsonValue value, [MaybeNullWhen(false)] out string discriminator)
{ {
discriminator = null!; discriminator = null!;
if (value is not JsonObject obj) if (value.Type != JsonValueType.Object)
{ {
return false; return false;
} }
if (!obj.TryGetValue<JsonString>(Discriminator, out var type)) if (!value.AsObject.TryGetValue(Discriminator, out var type) || type.Type != JsonValueType.String)
{ {
return false; return false;
} }
if (string.IsNullOrWhiteSpace(type.Value)) var typed = type.AsString;
if (string.IsNullOrWhiteSpace(typed))
{ {
return false; return false;
} }
discriminator = type.Value; discriminator = typed;
return true; return true;
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentData.cs

@ -62,7 +62,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
foreach (var (language, value) in fieldData.ToList()) foreach (var (language, value) in fieldData.ToList())
{ {
if (!otherField.TryGetValue(language, out var otherValue) || otherValue == null) if (!otherField.TryGetValue(language, out var otherValue))
{ {
continue; continue;
} }

40
backend/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs

@ -7,32 +7,37 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
{ {
public sealed class ContentFieldData : Dictionary<string, IJsonValue>, IEquatable<ContentFieldData> public sealed class ContentFieldData : ListDictionary<string, JsonValue>, IEquatable<ContentFieldData>
{ {
public ContentFieldData() public ContentFieldData()
: base(StringComparer.OrdinalIgnoreCase) : base(0, StringComparer.OrdinalIgnoreCase)
{ {
} }
public ContentFieldData(ContentFieldData source) public ContentFieldData(int capacity)
: base(source, StringComparer.OrdinalIgnoreCase) : base(capacity, StringComparer.OrdinalIgnoreCase)
{ {
} }
public ContentFieldData(int capacity) public ContentFieldData(ContentFieldData source)
: base(capacity, StringComparer.OrdinalIgnoreCase) : base(source.Count, StringComparer.OrdinalIgnoreCase)
{ {
foreach (var (key, value) in source)
{
this[key] = value;
}
} }
public bool TryGetNonNull(string key, [MaybeNullWhen(false)] out IJsonValue result) public bool TryGetNonNull(string key, [MaybeNullWhen(false)] out JsonValue result)
{ {
result = null!; result = JsonValue.Null;
if (TryGetValue(key, out var found) && found != null && found.Type != JsonValueType.Null) if (TryGetValue(key, out var found) && found.Type != JsonValueType.Null)
{ {
result = found; result = found;
return true; return true;
@ -41,19 +46,16 @@ namespace Squidex.Domain.Apps.Core.Contents
return false; return false;
} }
public ContentFieldData AddInvariant(object? value) public ContentFieldData AddInvariant(JsonValue value)
{ {
return AddValue(InvariantPartitioning.Key, JsonValue.Create(value)); this[InvariantPartitioning.Key] = value;
}
public ContentFieldData AddLocalized(string key, object? value) return this;
{
return AddValue(key, JsonValue.Create(value));
} }
public ContentFieldData AddValue(string key, IJsonValue? value) public ContentFieldData AddLocalized(string key, JsonValue value)
{ {
this[key] = JsonValue.Create(value); this[key] = value;
return this; return this;
} }
@ -64,7 +66,7 @@ namespace Squidex.Domain.Apps.Core.Contents
foreach (var (key, value) in this) foreach (var (key, value) in this)
{ {
clone[key] = value?.Clone()!; clone[key] = value.Clone()!;
} }
return clone; return clone;
@ -87,7 +89,7 @@ namespace Squidex.Domain.Apps.Core.Contents
public override string ToString() public override string ToString()
{ {
return $"{{{string.Join(", ", this.Select(x => $"\"{x.Key}\":{x.Value.ToJsonString()}"))}}}"; return $"{{{string.Join(", ", this.Select(x => $"\"{x.Key}\":{x.Value}"))}}}";
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Contents/FlatContentData.cs

@ -9,7 +9,7 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Contents namespace Squidex.Domain.Apps.Core.Contents
{ {
public sealed class FlatContentData : Dictionary<string, IJsonValue?> public sealed class FlatContentData : Dictionary<string, JsonValue>
{ {
} }
} }

17
backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs

@ -8,6 +8,7 @@
using GeoJSON.Net; using GeoJSON.Net;
using GeoJSON.Net.Geometry; using GeoJSON.Net.Geometry;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.ObjectPool; using Squidex.Infrastructure.ObjectPool;
@ -17,31 +18,33 @@ namespace Squidex.Domain.Apps.Core.Contents
{ {
public static class GeoJsonValue public static class GeoJsonValue
{ {
public static GeoJsonParseResult TryParse(IJsonValue value, IJsonSerializer serializer, out GeoJSONObject? geoJSON) public static GeoJsonParseResult TryParse(JsonValue value, IJsonSerializer serializer, out GeoJSONObject? geoJSON)
{ {
Guard.NotNull(serializer); Guard.NotNull(serializer);
Guard.NotNull(value); Guard.NotNull(value);
geoJSON = null; geoJSON = null;
if (value is JsonObject obj) if (value.Type == JsonValueType.Object)
{ {
var obj = value.AsObject;
if (TryParseGeoJson(obj, serializer, out geoJSON)) if (TryParseGeoJson(obj, serializer, out geoJSON))
{ {
return GeoJsonParseResult.Success; return GeoJsonParseResult.Success;
} }
if (!obj.TryGetValue<JsonNumber>("latitude", out var lat) || !lat.Value.IsBetween(-90, 90)) if (!obj.TryGetValue("latitude", out var lat) || lat.Type != JsonValueType.Number || !lat.AsNumber.IsBetween(-90, 90))
{ {
return GeoJsonParseResult.InvalidLatitude; return GeoJsonParseResult.InvalidLatitude;
} }
if (!obj.TryGetValue<JsonNumber>("longitude", out var lon) || !lon.Value.IsBetween(-180, 180)) if (!obj.TryGetValue("longitude", out var lon) || lon.Type != JsonValueType.Number || !lon.AsNumber.IsBetween(-180, 180))
{ {
return GeoJsonParseResult.InvalidLongitude; return GeoJsonParseResult.InvalidLongitude;
} }
geoJSON = new Point(new Position(lat.Value, lon.Value)); geoJSON = new Point(new Position(lat.AsNumber, lon.AsNumber));
return GeoJsonParseResult.Success; return GeoJsonParseResult.Success;
} }
@ -49,11 +52,11 @@ namespace Squidex.Domain.Apps.Core.Contents
return GeoJsonParseResult.InvalidValue; return GeoJsonParseResult.InvalidValue;
} }
private static bool TryParseGeoJson(JsonObject obj, IJsonSerializer serializer, out GeoJSONObject? geoJSON) private static bool TryParseGeoJson(ListDictionary<string, JsonValue> obj, IJsonSerializer serializer, out GeoJSONObject? geoJSON)
{ {
geoJSON = null; geoJSON = null;
if (!obj.TryGetValue("type", out var type) || type is not JsonString) if (!obj.TryGetValue("type", out var type) || type.Type != JsonValueType.String)
{ {
return false; return false;
} }

2
backend/src/Squidex.Domain.Apps.Core.Model/Contents/Json/ContentFieldDataConverter.cs

@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Core.Contents.Json
throw new JsonSerializationException("Unexpected end when reading Object."); throw new JsonSerializationException("Unexpected end when reading Object.");
} }
var value = serializer.Deserialize<IJsonValue>(reader)!; var value = serializer.Deserialize<JsonValue>(reader)!;
if (Language.IsDefault(propertyName) || propertyName == InvariantPartitioning.Key) if (Language.IsDefault(propertyName) || propertyName == InvariantPartitioning.Key)
{ {

4
backend/src/Squidex.Domain.Apps.Core.Model/Rules/Rule.cs

@ -79,7 +79,7 @@ namespace Squidex.Domain.Apps.Core.Rules
if (newTrigger.GetType() != Trigger.GetType()) if (newTrigger.GetType() != Trigger.GetType())
{ {
throw new ArgumentException("New trigger has another type.", nameof(newTrigger)); ThrowHelper.ArgumentException("New trigger has another type.", nameof(newTrigger));
} }
if (Trigger.Equals(newTrigger)) if (Trigger.Equals(newTrigger))
@ -100,7 +100,7 @@ namespace Squidex.Domain.Apps.Core.Rules
if (newAction.GetType() != Action.GetType()) if (newAction.GetType() != Action.GetType())
{ {
throw new ArgumentException("New action has another type.", nameof(newAction)); ThrowHelper.ArgumentException("New action has another type.", nameof(newAction));
} }
if (Action.Equals(newAction)) if (Action.Equals(newAction))

9
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldCollection.cs

@ -101,7 +101,7 @@ namespace Squidex.Domain.Apps.Core.Schemas
if (ids.Count != fieldsOrdered.Length || ids.Any(x => !ById.ContainsKey(x))) if (ids.Count != fieldsOrdered.Length || ids.Any(x => !ById.ContainsKey(x)))
{ {
throw new ArgumentException("Ids must cover all fields.", nameof(ids)); ThrowHelper.ArgumentException("Ids must cover all fields.", nameof(ids));
} }
if (ids.SequenceEqual(fieldsOrdered.Select(x => x.Id))) if (ids.SequenceEqual(fieldsOrdered.Select(x => x.Id)))
@ -119,12 +119,12 @@ namespace Squidex.Domain.Apps.Core.Schemas
if (ByName.ContainsKey(field.Name)) if (ByName.ContainsKey(field.Name))
{ {
throw new ArgumentException($"A field with name '{field.Name}' already exists.", nameof(field)); ThrowHelper.ArgumentException($"A field with name '{field.Name}' already exists.", nameof(field));
} }
if (ById.ContainsKey(field.Id)) if (ById.ContainsKey(field.Id))
{ {
throw new ArgumentException($"A field with ID {field.Id} already exists.", nameof(field)); ThrowHelper.ArgumentException($"A field with ID {field.Id} already exists.", nameof(field));
} }
return new FieldCollection<T>(fieldsOrdered.Union(Enumerable.Repeat(field, 1))); return new FieldCollection<T>(fieldsOrdered.Union(Enumerable.Repeat(field, 1)));
@ -149,7 +149,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
if (newField is null) if (newField is null)
{ {
throw new InvalidOperationException($"Field must be of type {typeof(T)}"); ThrowHelper.InvalidOperationException($"Field must be of type {typeof(T)}");
return default!;
} }
return new FieldCollection<T>(fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x)); return new FieldCollection<T>(fieldsOrdered.Select(x => ReferenceEquals(x, field) ? newField : x));

3
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/NestedField{T}.cs

@ -47,7 +47,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
if (newProperties is not T typedProperties) if (newProperties is not T typedProperties)
{ {
throw new ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties)); ThrowHelper.ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties));
return default!;
} }
return typedProperties; return typedProperties;

3
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/RootField{T}.cs

@ -47,7 +47,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
if (newProperties is not T typedProperties) if (newProperties is not T typedProperties)
{ {
throw new ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties)); ThrowHelper.ArgumentException($"Properties must be of type '{typeof(T)}", nameof(newProperties));
return default!;
} }
return typedProperties; return typedProperties;

4
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverter.cs

@ -54,9 +54,9 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return data; return data;
} }
for (var i = 0; i < converters.Length; i++) foreach (var converter in converters)
{ {
data = converters[i](data!, field)!; data = converter(data!, field)!;
if (data == null) if (data == null)
{ {

21
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ContentConverterFlat.cs

@ -35,9 +35,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
foreach (var (key, value) in content) foreach (var (key, value) in content)
{ {
var first = GetFirst(value, fallback); if (TryGetFirst(value, fallback, out var first))
if (first != null)
{ {
result[key] = first; result[key] = first;
} }
@ -61,29 +59,34 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return fieldData; return fieldData;
} }
private static IJsonValue? GetFirst(ContentFieldData? fieldData, string fallback) private static bool TryGetFirst(ContentFieldData? fieldData, string fallback, out JsonValue result)
{ {
result = JsonValue.Null;
if (fieldData == null) if (fieldData == null)
{ {
return null; return false;
} }
if (fieldData.Count == 1) if (fieldData.Count == 1)
{ {
return fieldData.Values.First(); result = fieldData.Values.First();
return true;
} }
if (fieldData.TryGetValue(fallback, out var value)) if (fieldData.TryGetValue(fallback, out var value))
{ {
return value; result = value;
return true;
} }
if (fieldData.Count > 1) if (fieldData.Count > 1)
{ {
return fieldData.Values.First(); result = fieldData.Values.First();
return true;
} }
return null; return false;
} }
} }
} }

82
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs

@ -219,10 +219,10 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
newData ??= new ContentFieldData(data); newData ??= new ContentFieldData(data);
newData.Remove(key); newData.Remove(key);
} }
else if (!ReferenceEquals(newValue, value)) else if (!ReferenceEquals(newValue.Value.Value, value.Value))
{ {
newData ??= new ContentFieldData(data); newData ??= new ContentFieldData(data);
newData[key] = newValue; newData[key] = newValue.Value;
} }
} }
@ -230,7 +230,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
}; };
} }
private static IJsonValue? ConvertByType<T>(T field, IJsonValue? value, IArrayField? parent, ValueConverter[] converters, private static JsonValue? ConvertByType<T>(T field, JsonValue value, IArrayField? parent, ValueConverter[] converters,
ResolvedComponents components) where T : IField ResolvedComponents components) where T : IField
{ {
switch (field) switch (field)
@ -249,16 +249,20 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
} }
} }
private static IJsonValue? ConvertArray(IArrayField field, IJsonValue? value, ValueConverter[] converters, private static JsonValue? ConvertArray(IArrayField field, JsonValue value, ValueConverter[] converters,
ResolvedComponents components) ResolvedComponents components)
{ {
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
var array = value.AsArray;
JsonArray? result = null; JsonArray? result = null;
for (int i = 0, j = 0; i < array.Count; i++, j++) for (int i = 0, j = 0; i < array.Count; i++, j++)
{ {
var newValue = ConvertArrayItem(field, array[i], converters, components); var oldValue = array[i];
var newValue = ConvertArrayItem(field, oldValue, converters, components);
if (newValue == null) if (newValue == null)
{ {
@ -266,29 +270,33 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
result.RemoveAt(j); result.RemoveAt(j);
j--; j--;
} }
else if (!ReferenceEquals(newValue, array[i])) else if (!ReferenceEquals(newValue.Value.Value, oldValue.Value))
{ {
result ??= new JsonArray(array); result ??= new JsonArray(array);
result[j] = newValue; result[j] = newValue.Value;
} }
} }
return result ?? array; return result ?? value;
} }
return null; return null;
} }
private static IJsonValue? ConvertComponents(IJsonValue? value, ValueConverter[] converters, private static JsonValue? ConvertComponents(JsonValue? value, ValueConverter[] converters,
ResolvedComponents components) ResolvedComponents components)
{ {
if (value is JsonArray array) if (value?.Type == JsonValueType.Array)
{ {
var array = value.Value.AsArray;
JsonArray? result = null; JsonArray? result = null;
for (int i = 0, j = 0; i < array.Count; i++, j++) for (int i = 0, j = 0; i < array.Count; i++, j++)
{ {
var newValue = ConvertComponent(array[i], converters, components); var oldValue = array[i];
var newValue = ConvertComponent(oldValue, converters, components);
if (newValue == null) if (newValue == null)
{ {
@ -296,58 +304,60 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
result.RemoveAt(j); result.RemoveAt(j);
j--; j--;
} }
else if (!ReferenceEquals(newValue, array[i])) else if (!ReferenceEquals(newValue.Value.Value, array[i].Value))
{ {
result ??= new JsonArray(array); result ??= new JsonArray(array);
result[j] = newValue; result[j] = newValue.Value;
} }
} }
return result ?? array; return result ?? value;
} }
return null; return null;
} }
private static IJsonValue? ConvertComponent(IJsonValue? value, ValueConverter[] converters, private static JsonValue? ConvertComponent(JsonValue? value, ValueConverter[] converters,
ResolvedComponents components) ResolvedComponents components)
{ {
if (value is JsonObject obj && obj.TryGetValue<JsonString>(Component.Discriminator, out var type)) if (value.HasValue && value.Value.Type == JsonValueType.Object && value.Value.AsObject.TryGetValue(Component.Discriminator, out var type) && type.Type == JsonValueType.String)
{ {
var id = DomainId.Create(type.Value); var id = DomainId.Create(type.AsString);
if (components.TryGetValue(id, out var schema)) if (components.TryGetValue(id, out var schema))
{ {
return ConvertNested(schema.FieldCollection, obj, null, converters, components); return ConvertNested(schema.FieldCollection, value.Value, null, converters, components);
} }
else else
{ {
return obj; return value;
} }
} }
return null; return null;
} }
private static IJsonValue? ConvertArrayItem(IArrayField field, IJsonValue? value, ValueConverter[] converters, private static JsonValue? ConvertArrayItem(IArrayField field, JsonValue value, ValueConverter[] converters,
ResolvedComponents components) ResolvedComponents components)
{ {
if (value is JsonObject obj) if (value.Type == JsonValueType.Object)
{ {
return ConvertNested(field.FieldCollection, obj, field, converters, components); return ConvertNested(field.FieldCollection, value, field, converters, components);
} }
return null; return null;
} }
private static IJsonValue ConvertNested<T>(FieldCollection<T> fields, JsonObject source, IArrayField? parent, ValueConverter[] converters, private static JsonValue ConvertNested<T>(FieldCollection<T> fields, JsonValue source, IArrayField? parent, ValueConverter[] converters,
ResolvedComponents components) where T : IField ResolvedComponents components) where T : IField
{ {
JsonObject? result = null; JsonObject? result = null;
foreach (var (key, value) in source) var obj = source.AsObject;
foreach (var (key, value) in obj)
{ {
var newValue = value; JsonValue? newValue = value;
if (fields.ByName.TryGetValue(key, out var field)) if (fields.ByName.TryGetValue(key, out var field))
{ {
@ -360,30 +370,34 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
if (newValue == null) if (newValue == null)
{ {
result ??= new JsonObject(source); result ??= new JsonObject(obj);
result.Remove(key); result.Remove(key);
} }
else if (!ReferenceEquals(newValue, value)) else if (!ReferenceEquals(newValue.Value.Value, value.Value))
{ {
result ??= new JsonObject(source); result ??= new JsonObject(obj);
result[key] = newValue; result[key] = newValue.Value;
} }
} }
return result ?? source; return result ?? source;
} }
private static IJsonValue? ConvertValue(IField field, IJsonValue? value, IArrayField? parent, ValueConverter[] converters) private static JsonValue? ConvertValue(IField field, JsonValue value, IArrayField? parent, ValueConverter[] converters)
{ {
var newValue = value; var newValue = value;
for (var i = 0; i < converters.Length; i++) for (var i = 0; i < converters.Length; i++)
{ {
newValue = converters[i](newValue!, field, parent); var candidate = converters[i](newValue!, field, parent);
if (newValue == null) if (candidate == null)
{ {
break; return null;
}
else
{
newValue = candidate.Value;
} }
} }

28
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/StringFormatter.cs

@ -17,22 +17,22 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
private static readonly StringFormatter Instance = new StringFormatter(); private static readonly StringFormatter Instance = new StringFormatter();
public record struct Args(IJsonValue Value); public record struct Args(JsonValue Value);
private StringFormatter() private StringFormatter()
{ {
} }
public static string Format(IField field, IJsonValue? value) public static string Format(IField field, JsonValue value)
{ {
Guard.NotNull(field); Guard.NotNull(field);
if (value == null || value is JsonNull) if (value.Type == JsonValueType.Null)
{ {
return string.Empty; return string.Empty;
} }
var args = new Args(value ?? JsonValue.Null); var args = new Args(value);
return field.RawProperties.Accept(Instance, args); return field.RawProperties.Accept(Instance, args);
} }
@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public string Visit(BooleanFieldProperties properties, Args args) public string Visit(BooleanFieldProperties properties, Args args)
{ {
if (args.Value is JsonBoolean { Value: true }) if (args.Value.Type == JsonValueType.Boolean && args.Value.AsBoolean)
{ {
return "Yes"; return "Yes";
} }
@ -76,11 +76,11 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public string Visit(GeolocationFieldProperties properties, Args args) public string Visit(GeolocationFieldProperties properties, Args args)
{ {
if (args.Value is JsonObject jsonObject && if (args.Value.Type == JsonValueType.Object &&
jsonObject.TryGetValue<JsonNumber>("latitude", out var lat) && args.Value.TryGetValue(JsonValueType.Number, "latitude", out var lat) &&
jsonObject.TryGetValue<JsonNumber>("longitude", out var lon)) args.Value.TryGetValue(JsonValueType.Number, "longitude", out var lon))
{ {
return $"{lat}, {lon}"; return $"{lat.AsNumber}, {lon.AsNumber}";
} }
else else
{ {
@ -117,9 +117,9 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public string Visit(TagsFieldProperties properties, Args args) public string Visit(TagsFieldProperties properties, Args args)
{ {
if (args.Value is JsonArray array) if (args.Value.Type == JsonValueType.Array)
{ {
return string.Join(", ", array); return string.Join(", ", args.Value.AsArray);
} }
else else
{ {
@ -132,10 +132,12 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return string.Empty; return string.Empty;
} }
private static string FormatArray(IJsonValue value, string singularName, string pluralName) private static string FormatArray(JsonValue value, string singularName, string pluralName)
{ {
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
var array = value.AsArray;
if (array.Count > 1) if (array.Count > 1)
{ {
return $"{array.Count} {pluralName}"; return $"{array.Count} {pluralName}";

10
backend/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/ValueConverters.cs

@ -15,7 +15,7 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ConvertContent namespace Squidex.Domain.Apps.Core.ConvertContent
{ {
public delegate IJsonValue? ValueConverter(IJsonValue value, IField field, IArrayField? parent); public delegate JsonValue? ValueConverter(JsonValue value, IField field, IArrayField? parent);
public static class ValueConverters public static class ValueConverters
{ {
@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static readonly ValueConverter ExcludeHidden = (value, field, parent) => public static readonly ValueConverter ExcludeHidden = (value, field, parent) =>
{ {
return field.IsForApi() ? value : null; return field.IsForApi() ? (JsonValue?)value : null;
}; };
public static ValueConverter ExcludeChangedTypes(IJsonSerializer jsonSerializer) public static ValueConverter ExcludeChangedTypes(IJsonSerializer jsonSerializer)
@ -96,13 +96,15 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
return (value, field, parent) => return (value, field, parent) =>
{ {
if (field is IField<AssetsFieldProperties> && value is JsonArray array && shouldHandle(field, parent)) if (field is IField<AssetsFieldProperties> && value.Type == JsonValueType.Array && shouldHandle(field, parent))
{ {
var array = value.AsArray;
for (var i = 0; i < array.Count; i++) for (var i = 0; i < array.Count; i++)
{ {
var id = array[i].ToString(); var id = array[i].ToString();
array[i] = JsonValue.Create(urlGenerator.AssetContent(appId, id)); array[i] = urlGenerator.AssetContent(appId, id);
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueExtensions.cs

@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
{ {
var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant(), partitionKey); var defaultValue = DefaultValueFactory.CreateDefaultValue(field, SystemClock.Instance.GetCurrentInstant(), partitionKey);
if (field.RawProperties.IsRequired || defaultValue == null || defaultValue.Type == JsonValueType.Null) if (field.RawProperties.IsRequired || defaultValue.Type == JsonValueType.Null)
{ {
return; return;
} }

50
backend/src/Squidex.Domain.Apps.Core.Operations/DefaultValues/DefaultValueFactory.cs

@ -15,7 +15,7 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.DefaultValues namespace Squidex.Domain.Apps.Core.DefaultValues
{ {
public sealed class DefaultValueFactory : IFieldPropertiesVisitor<IJsonValue, DefaultValueFactory.Args> public sealed class DefaultValueFactory : IFieldPropertiesVisitor<JsonValue, DefaultValueFactory.Args>
{ {
private static readonly DefaultValueFactory Instance = new DefaultValueFactory(); private static readonly DefaultValueFactory Instance = new DefaultValueFactory();
@ -25,87 +25,89 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
{ {
} }
public static IJsonValue CreateDefaultValue(IField field, Instant now, string partition) public static JsonValue CreateDefaultValue(IField field, Instant now, string partition)
{ {
Guard.NotNull(field); Guard.NotNull(field);
Guard.NotNull(partition); Guard.NotNull(partition);
return field.RawProperties.Accept(Instance, new Args(now, partition)); var x = field.RawProperties.Accept(Instance, new Args(now, partition));
return x;
} }
public IJsonValue Visit(ArrayFieldProperties properties, Args args) public JsonValue Visit(ArrayFieldProperties properties, Args args)
{ {
return JsonValue.Array(); return new JsonArray();
} }
public IJsonValue Visit(AssetsFieldProperties properties, Args args) public JsonValue Visit(AssetsFieldProperties properties, Args args)
{ {
var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition); var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition);
return Array(value); return Array(value);
} }
public IJsonValue Visit(BooleanFieldProperties properties, Args args) public JsonValue Visit(BooleanFieldProperties properties, Args args)
{ {
var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition); var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition);
return JsonValue.Create(value); return value ?? JsonValue.Null;
} }
public IJsonValue Visit(ComponentFieldProperties properties, Args args) public JsonValue Visit(ComponentFieldProperties properties, Args args)
{ {
return JsonValue.Null; return JsonValue.Null;
} }
public IJsonValue Visit(ComponentsFieldProperties properties, Args args) public JsonValue Visit(ComponentsFieldProperties properties, Args args)
{ {
return JsonValue.Array(); return new JsonArray();
} }
public IJsonValue Visit(GeolocationFieldProperties properties, Args args) public JsonValue Visit(GeolocationFieldProperties properties, Args args)
{ {
return JsonValue.Null; return JsonValue.Null;
} }
public IJsonValue Visit(JsonFieldProperties properties, Args args) public JsonValue Visit(JsonFieldProperties properties, Args args)
{ {
return JsonValue.Null; return JsonValue.Null;
} }
public IJsonValue Visit(NumberFieldProperties properties, Args args) public JsonValue Visit(NumberFieldProperties properties, Args args)
{ {
var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition); var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition);
return JsonValue.Create(value); return value ?? JsonValue.Null;
} }
public IJsonValue Visit(ReferencesFieldProperties properties, Args args) public JsonValue Visit(ReferencesFieldProperties properties, Args args)
{ {
var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition); var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition);
return Array(value); return Array(value);
} }
public IJsonValue Visit(StringFieldProperties properties, Args args) public JsonValue Visit(StringFieldProperties properties, Args args)
{ {
var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition); var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition);
return JsonValue.Create(value); return value;
} }
public IJsonValue Visit(TagsFieldProperties properties, Args args) public JsonValue Visit(TagsFieldProperties properties, Args args)
{ {
var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition); var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition);
return Array(value); return Array(value);
} }
public IJsonValue Visit(UIFieldProperties properties, Args args) public JsonValue Visit(UIFieldProperties properties, Args args)
{ {
return JsonValue.Null; return JsonValue.Null;
} }
public IJsonValue Visit(DateTimeFieldProperties properties, Args args) public JsonValue Visit(DateTimeFieldProperties properties, Args args)
{ {
if (properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now) if (properties.CalculatedDefaultValue == DateTimeCalculatedDefaultValue.Now)
{ {
@ -119,7 +121,7 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition); var value = GetDefaultValue(properties.DefaultValue, properties.DefaultValues, args.Partition);
return JsonValue.Create(value); return value ?? JsonValue.Null;
} }
private static T GetDefaultValue<T>(T value, LocalizedValue<T>? values, string partition) private static T GetDefaultValue<T>(T value, LocalizedValue<T>? values, string partition)
@ -132,7 +134,7 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
return value; return value;
} }
private static IJsonValue Array(IEnumerable<string>? values) private static JsonValue Array(IEnumerable<string>? values)
{ {
if (values != null) if (values != null)
{ {
@ -140,7 +142,7 @@ namespace Squidex.Domain.Apps.Core.DefaultValues
} }
else else
{ {
return JsonValue.Array(); return new JsonArray();
} }
} }
} }

17
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs

@ -22,16 +22,16 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return false; return false;
} }
static bool CanHaveReference(IJsonValue value) static bool CanHaveReference(JsonValue value)
{ {
if (value is JsonArray) if (value.Type == JsonValueType.Array)
{ {
return true; return true;
} }
if (value is JsonObject obj) if (value.Type == JsonValueType.Object)
{ {
foreach (var (_, nested) in obj) foreach (var (_, nested) in value.AsObject)
{ {
if (CanHaveReference(nested)) if (CanHaveReference(nested))
{ {
@ -107,17 +107,14 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
} }
} }
public static HashSet<DomainId> GetReferencedIds(this IField field, IJsonValue? value, public static HashSet<DomainId> GetReferencedIds(this IField field, JsonValue value,
ResolvedComponents components, int referencesPerField = int.MaxValue) ResolvedComponents components, int referencesPerField = int.MaxValue)
{ {
Guard.NotNull(components); Guard.NotNull(components);
var result = new HashSet<DomainId>(); var result = new HashSet<DomainId>();
if (value != null)
{
ReferencesExtractor.Extract(field, value, result, referencesPerField, components); ReferencesExtractor.Extract(field, value, result, referencesPerField, components);
}
return result; return result;
} }
@ -127,11 +124,11 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
Guard.NotNull(schema); Guard.NotNull(schema);
Guard.NotNull(partitioning); Guard.NotNull(partitioning);
var result = JsonValue.Object(); var result = new JsonObject();
foreach (var partitionKey in partitioning.AllKeys) foreach (var partitionKey in partitioning.AllKeys)
{ {
result[partitionKey] = JsonValue.Create(data.FormatReferenceFields(schema, partitionKey, separator)); result[partitionKey] = data.FormatReferenceFields(schema, partitionKey, separator);
} }
return result; return result;

48
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesCleaner.cs

@ -13,93 +13,95 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
internal sealed class ReferencesCleaner : IFieldVisitor<IJsonValue, ReferencesCleaner.Args> internal sealed class ReferencesCleaner : IFieldVisitor<JsonValue, ReferencesCleaner.Args>
{ {
private static readonly ReferencesCleaner Instance = new ReferencesCleaner(); private static readonly ReferencesCleaner Instance = new ReferencesCleaner();
public record struct Args(IJsonValue Value, ISet<DomainId> ValidIds); public record struct Args(JsonValue Value, ISet<DomainId> ValidIds);
private ReferencesCleaner() private ReferencesCleaner()
{ {
} }
public static IJsonValue Cleanup(IField field, IJsonValue? value, HashSet<DomainId> validIds) public static JsonValue Cleanup(IField field, JsonValue value, HashSet<DomainId> validIds)
{ {
var args = new Args(value ?? JsonValue.Null, validIds); var args = new Args(value, validIds);
return field.Accept(Instance, args); return field.Accept(Instance, args);
} }
public IJsonValue Visit(IField<AssetsFieldProperties> field, Args args) public JsonValue Visit(IField<AssetsFieldProperties> field, Args args)
{ {
return CleanIds(args); return CleanIds(args);
} }
public IJsonValue Visit(IField<ReferencesFieldProperties> field, Args args) public JsonValue Visit(IField<ReferencesFieldProperties> field, Args args)
{ {
return CleanIds(args); return CleanIds(args);
} }
public IJsonValue Visit(IField<BooleanFieldProperties> field, Args args) public JsonValue Visit(IField<BooleanFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<ComponentFieldProperties> field, Args args) public JsonValue Visit(IField<ComponentFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<ComponentsFieldProperties> field, Args args) public JsonValue Visit(IField<ComponentsFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<DateTimeFieldProperties> field, Args args) public JsonValue Visit(IField<DateTimeFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<GeolocationFieldProperties> field, Args args) public JsonValue Visit(IField<GeolocationFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<JsonFieldProperties> field, Args args) public JsonValue Visit(IField<JsonFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<NumberFieldProperties> field, Args args) public JsonValue Visit(IField<NumberFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<StringFieldProperties> field, Args args) public JsonValue Visit(IField<StringFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<TagsFieldProperties> field, Args args) public JsonValue Visit(IField<TagsFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IField<UIFieldProperties> field, Args args) public JsonValue Visit(IField<UIFieldProperties> field, Args args)
{ {
return args.Value; return args.Value;
} }
public IJsonValue Visit(IArrayField field, Args args) public JsonValue Visit(IArrayField field, Args args)
{ {
return args.Value; return args.Value;
} }
private static IJsonValue CleanIds(Args args) private static JsonValue CleanIds(Args args)
{ {
if (args.Value is JsonArray array) if (args.Value.Type == JsonValueType.Array)
{ {
var result = array; var array = args.Value.AsArray;
var result = args.Value.AsArray;
for (var i = 0; i < result.Count; i++) for (var i = 0; i < result.Count; i++)
{ {
@ -107,7 +109,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
if (ReferenceEquals(result, array)) if (ReferenceEquals(result, array))
{ {
result = new JsonArray(array); result = array;
} }
result.RemoveAt(i); result.RemoveAt(i);
@ -121,9 +123,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return args.Value; return args.Value;
} }
private static bool IsValidReference(IJsonValue item, Args args) private static bool IsValidReference(JsonValue item, Args args)
{ {
return item is JsonString s && args.ValidIds.Contains(DomainId.Create(s.Value)); return item.Type == JsonValueType.String && args.ValidIds.Contains(DomainId.Create(item.AsString));
} }
} }
} }

43
backend/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -18,26 +18,26 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
private static readonly ReferencesExtractor Instance = new ReferencesExtractor(); private static readonly ReferencesExtractor Instance = new ReferencesExtractor();
public record struct Args(IJsonValue Value, ISet<DomainId> Result, int Take, ResolvedComponents Components); public record struct Args(JsonValue Value, ISet<DomainId> Result, int Take, ResolvedComponents Components);
private ReferencesExtractor() private ReferencesExtractor()
{ {
} }
public static None Extract(IField field, IJsonValue? value, HashSet<DomainId> result, int take, ResolvedComponents components) public static None Extract(IField field, JsonValue value, HashSet<DomainId> result, int take, ResolvedComponents components)
{ {
var args = new Args(value ?? JsonValue.Null, result, take, components); var args = new Args(value, result, take, components);
return field.Accept(Instance, args); return field.Accept(Instance, args);
} }
public None Visit(IArrayField field, Args args) public None Visit(IArrayField field, Args args)
{ {
if (args.Value is JsonArray array) if (args.Value.Type == JsonValueType.Array)
{ {
for (var i = 0; i < array.Count; i++) foreach (var value in args.Value.AsArray)
{ {
ExtractFromArrayItem(field, array[i], args); ExtractFromArrayItem(field, value, args);
} }
} }
@ -72,11 +72,11 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
public None Visit(IField<ComponentsFieldProperties> field, Args args) public None Visit(IField<ComponentsFieldProperties> field, Args args)
{ {
if (args.Value is JsonArray array) if (args.Value.Type == JsonValueType.Array)
{ {
for (var i = 0; i < array.Count; i++) foreach (var value in args.Value.AsArray)
{ {
ExtractFromComponent(array[i], args); ExtractFromComponent(value, args);
} }
} }
@ -118,10 +118,12 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
return None.Value; return None.Value;
} }
private void ExtractFromArrayItem(IArrayField field, IJsonValue value, Args args) private void ExtractFromArrayItem(IArrayField field, JsonValue value, Args args)
{ {
if (value is JsonObject obj) if (value.Type == JsonValueType.Object)
{ {
var obj = value.AsObject;
foreach (var nestedField in field.Fields) foreach (var nestedField in field.Fields)
{ {
if (obj.TryGetValue(nestedField.Name, out var nestedValue)) if (obj.TryGetValue(nestedField.Name, out var nestedValue))
@ -132,11 +134,15 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
} }
} }
private void ExtractFromComponent(IJsonValue value, Args args) private void ExtractFromComponent(JsonValue value, Args args)
{
if (value.Type == JsonValueType.Object)
{ {
if (value is JsonObject obj && obj.TryGetValue<JsonString>(Component.Discriminator, out var type)) var obj = value.AsObject;
if (obj.TryGetValue(Component.Discriminator, out var type) && type.Type == JsonValueType.String)
{ {
var id = DomainId.Create(type.Value); var id = DomainId.Create(type.AsString);
if (args.Components.TryGetValue(id, out var schema)) if (args.Components.TryGetValue(id, out var schema))
{ {
@ -150,18 +156,19 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
} }
} }
} }
}
private static void AddIds(ref Args args) private static void AddIds(ref Args args)
{ {
var added = 0; var added = 0;
if (args.Value is JsonArray array) if (args.Value.Type == JsonValueType.Array)
{ {
foreach (var id in array) foreach (var id in args.Value.AsArray)
{ {
if (id is JsonString s) if (id.Type == JsonValueType.String)
{ {
args.Result.Add(DomainId.Create(s.Value)); args.Result.Add(DomainId.Create(id.AsString));
added++; added++;

6
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleVariable.cs

@ -36,16 +36,16 @@ namespace Squidex.Domain.Apps.Core.HandleRules
} }
else if (current is ContentFieldData field) else if (current is ContentFieldData field)
{ {
if (!field.TryGetValue(segment, out var temp) || temp == null) if (!field.TryGetValue(segment, out var temp))
{ {
break; break;
} }
current = temp; current = temp;
} }
else if (current is IJsonValue json) else if (current is JsonValue json)
{ {
if (!json.TryGet(segment, out var temp) || temp == null || temp.Type == JsonValueType.Null) if (!json.TryGetValue(segment, out var temp) || temp == JsonValue.Null)
{ {
break; break;
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/AssetEntityScriptVars.cs

@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
[FieldDescription(nameof(FieldDescriptions.AssetMetadata))] [FieldDescription(nameof(FieldDescriptions.AssetMetadata))]
public AssetMetadata? Metadata public AssetMetadata? Metadata
{ {
set => SetValue(value != null ? new ReadOnlyDictionary<string, IJsonValue>(value) : null); set => SetValue(value != null ? new ReadOnlyDictionary<string, JsonValue>(value) : null);
} }
[FieldDescription(nameof(FieldDescriptions.AssetTags))] [FieldDescription(nameof(FieldDescriptions.AssetTags))]

10
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldProperty.cs

@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
public sealed class ContentFieldProperty : CustomProperty public sealed class ContentFieldProperty : CustomProperty
{ {
private readonly ContentFieldObject contentField; private readonly ContentFieldObject contentField;
private IJsonValue contentValue; private JsonValue contentValue;
private JsValue? value; private JsValue? value;
private bool isChanged; private bool isChanged;
@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{ {
if (value == null) if (value == null)
{ {
if (contentValue != null) if (contentValue != default)
{ {
value = JsonMapper.Map(contentValue, contentField.Engine); value = JsonMapper.Map(contentValue, contentField.Engine);
} }
@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
} }
} }
public IJsonValue ContentValue public JsonValue ContentValue
{ {
get => contentValue; get => contentValue;
} }
@ -59,10 +59,10 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
get => isChanged; get => isChanged;
} }
public ContentFieldProperty(ContentFieldObject contentField, IJsonValue? contentValue = null) public ContentFieldProperty(ContentFieldObject contentField, JsonValue contentValue = default)
{ {
this.contentField = contentField; this.contentField = contentField;
this.contentValue = contentValue ?? JsonValue.Null; this.contentValue = contentValue;
} }
} }
} }

70
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/JsonMapper.cs

@ -9,39 +9,37 @@ using System.Globalization;
using Jint; using Jint;
using Jint.Native; using Jint.Native;
using Jint.Native.Object; using Jint.Native.Object;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{ {
public static class JsonMapper public static class JsonMapper
{ {
public static JsValue Map(IJsonValue? value, Engine engine) public static JsValue Map(JsonValue value, Engine engine)
{ {
if (value == null) switch (value.Type)
{ {
case JsonValueType.Null:
return JsValue.Null; return JsValue.Null;
} case JsonValueType.String:
return new JsString(value.AsString);
switch (value) case JsonValueType.Boolean:
{ return new JsBoolean(value.AsBoolean);
case JsonNull: case JsonValueType.Number:
return new JsNumber(value.AsNumber);
case JsonValueType.Object:
return FromObject(value.AsObject, engine);
case JsonValueType.Array:
return FromArray(value.AsArray, engine);
}
ThrowInvalidType(nameof(value));
return JsValue.Null; return JsValue.Null;
case JsonString s:
return new JsString(s.Value);
case JsonBoolean b:
return new JsBoolean(b.Value);
case JsonNumber b:
return new JsNumber(b.Value);
case JsonObject obj:
return FromObject(obj, engine);
case JsonArray arr:
return FromArray(arr, engine);
}
throw new ArgumentException("Invalid json type.", nameof(value));
} }
private static JsValue FromArray(JsonArray arr, Engine engine) private static JsValue FromArray(List<JsonValue> arr, Engine engine)
{ {
var target = new JsValue[arr.Count]; var target = new JsValue[arr.Count];
@ -53,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
return engine.Realm.Intrinsics.Array.Construct(target); return engine.Realm.Intrinsics.Array.Construct(target);
} }
private static JsValue FromObject(JsonObject obj, Engine engine) private static JsValue FromObject(ListDictionary<string, JsonValue> obj, Engine engine)
{ {
var target = new ObjectInstance(engine); var target = new ObjectInstance(engine);
@ -65,31 +63,31 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
return target; return target;
} }
public static IJsonValue Map(JsValue? value) public static JsonValue Map(JsValue? value)
{ {
if (value == null || value.IsNull() || value.IsUndefined()) if (value == null || value.IsNull() || value.IsUndefined())
{ {
return JsonValue.Null; return default;
} }
if (value.IsString()) if (value.IsString())
{ {
return JsonValue.Create(value.AsString()); return value.AsString();
} }
if (value.IsBoolean()) if (value.IsBoolean())
{ {
return JsonValue.Create(value.AsBoolean()); return value.AsBoolean();
} }
if (value.IsDate()) if (value.IsDate())
{ {
return JsonValue.Create(value.AsDate().ToString()); return value.AsDate().ToString();
} }
if (value.IsRegExp()) if (value.IsRegExp())
{ {
return JsonValue.Create(value.AsRegExp().Value?.ToString()); return value.AsRegExp().Value?.ToString();
} }
if (value.IsNumber()) if (value.IsNumber())
@ -98,17 +96,17 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
if (double.IsNaN(number) || double.IsPositiveInfinity(number) || double.IsNegativeInfinity(number)) if (double.IsNaN(number) || double.IsPositiveInfinity(number) || double.IsNegativeInfinity(number))
{ {
return JsonValue.Zero; return 0;
} }
return JsonValue.Create(number); return number;
} }
if (value.IsArray()) if (value.IsArray())
{ {
var arr = value.AsArray(); var arr = value.AsArray();
var result = JsonValue.Array(); var result = new JsonArray((int)arr.Length);
for (var i = 0; i < arr.Length; i++) for (var i = 0; i < arr.Length; i++)
{ {
@ -122,7 +120,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
{ {
var obj = value.AsObject(); var obj = value.AsObject();
var result = JsonValue.Object(); var result = new JsonObject((int)obj.Length);
foreach (var (key, propertyDescriptor) in obj.GetOwnProperties()) foreach (var (key, propertyDescriptor) in obj.GetOwnProperties())
{ {
@ -132,7 +130,13 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper
return result; return result;
} }
throw new ArgumentException("Invalid json type.", nameof(value)); ThrowInvalidType(nameof(value));
return default;
}
private static void ThrowInvalidType(string argument)
{
ThrowHelper.ArgumentException("Invalid json type.", argument);
} }
} }
} }

6
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/IScriptEngine.cs

@ -12,19 +12,19 @@ namespace Squidex.Domain.Apps.Core.Scripting
{ {
public interface IScriptEngine public interface IScriptEngine
{ {
Task<IJsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default, Task<JsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default,
CancellationToken ct = default); CancellationToken ct = default);
Task<ContentData> TransformAsync(DataScriptVars vars, string script, ScriptOptions options = default, Task<ContentData> TransformAsync(DataScriptVars vars, string script, ScriptOptions options = default,
CancellationToken ct = default); CancellationToken ct = default);
IJsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default); JsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default);
bool Evaluate(ScriptVars vars, string script, ScriptOptions options = default) bool Evaluate(ScriptVars vars, string script, ScriptOptions options = default)
{ {
try try
{ {
return Execute(vars, script, options).Equals(JsonValue.True); return Execute(vars, script, options).Equals(true);
} }
catch catch
{ {

8
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/JintScriptEngine.cs

@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
this.extensions = extensions?.ToArray() ?? Array.Empty<IJintExtension>(); this.extensions = extensions?.ToArray() ?? Array.Empty<IJintExtension>();
} }
public async Task<IJsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default, public async Task<JsonValue> ExecuteAsync(ScriptVars vars, string script, ScriptOptions options = default,
CancellationToken ct = default) CancellationToken ct = default)
{ {
Guard.NotNull(vars); Guard.NotNull(vars);
@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
using (var combined = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, ct)) using (var combined = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, ct))
{ {
var context = var context =
CreateEngine<IJsonValue>(options, combined.Token) CreateEngine<JsonValue?>(options, combined.Token)
.Extend(vars, options) .Extend(vars, options)
.Extend(extensions) .Extend(extensions)
.ExtendAsync(extensions); .ExtendAsync(extensions);
@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
} }
} }
public IJsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default) public JsonValue Execute(ScriptVars vars, string script, ScriptOptions options = default)
{ {
Guard.NotNull(vars); Guard.NotNull(vars);
Guard.NotNullOrEmpty(script); Guard.NotNullOrEmpty(script);
@ -124,7 +124,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
return JsonMapper.Map(result); return JsonMapper.Map(result);
} }
private ScriptExecutionContext<T> CreateEngine<T>(ScriptOptions options, CancellationToken ct) where T : class private ScriptExecutionContext<T> CreateEngine<T>(ScriptOptions options, CancellationToken ct)
{ {
if (Debugger.IsAttached) if (Debugger.IsAttached)
{ {

6
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs

@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
void Run<T>(Action<T>? action, T argument); void Run<T>(Action<T>? action, T argument);
} }
public sealed class ScriptExecutionContext<T> : ScriptExecutionContext, IScheduler where T : class public sealed class ScriptExecutionContext<T> : ScriptExecutionContext, IScheduler
{ {
private readonly TaskCompletionSource<T?> tcs = new TaskCompletionSource<T?>(); private readonly TaskCompletionSource<T?> tcs = new TaskCompletionSource<T?>();
private readonly CancellationToken cancellationToken; private readonly CancellationToken cancellationToken;
@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
{ {
if (pendingTasks <= 0) if (pendingTasks <= 0)
{ {
tcs.TrySetResult(null); tcs.TrySetResult(default);
} }
return tcs.Task.WithCancellation(cancellationToken); return tcs.Task.WithCancellation(cancellationToken);
@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Core.Scripting
if (Interlocked.Decrement(ref pendingTasks) <= 0) if (Interlocked.Decrement(ref pendingTasks) <= 0)
{ {
tcs.TrySetResult(null); tcs.TrySetResult(default);
} }
} }
catch (Exception ex) catch (Exception ex)

36
backend/src/Squidex.Domain.Apps.Core.Operations/Tags/TagNormalizer.cs

@ -21,10 +21,10 @@ namespace Squidex.Domain.Apps.Core.Tags
Guard.NotNull(newData); Guard.NotNull(newData);
var newValues = new HashSet<string>(); var newValues = new HashSet<string>();
var newArrays = new List<JsonArray>(); var newArrays = new List<JsonValue>();
var oldValues = new HashSet<string>(); var oldValues = new HashSet<string>();
var oldArrays = new List<JsonArray>(); var oldArrays = new List<JsonValue>();
GetValues(schema, newValues, newArrays, newData); GetValues(schema, newValues, newArrays, newData);
@ -37,13 +37,15 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
var normalized = await tagService.NormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), newValues, oldValues); var normalized = await tagService.NormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), newValues, oldValues);
foreach (var array in newArrays) foreach (var source in newArrays)
{ {
var array = source.AsArray;
for (var i = 0; i < array.Count; i++) for (var i = 0; i < array.Count; i++)
{ {
if (normalized.TryGetValue(array[i].ToString(), out var result)) if (normalized.TryGetValue(array[i].ToString(), out var result))
{ {
array[i] = JsonValue.Create(result); array[i] = result;
} }
} }
} }
@ -56,7 +58,7 @@ namespace Squidex.Domain.Apps.Core.Tags
Guard.NotNull(schema); Guard.NotNull(schema);
var tagsValues = new HashSet<string>(); var tagsValues = new HashSet<string>();
var tagsArrays = new List<JsonArray>(); var tagsArrays = new List<JsonValue>();
GetValues(schema, tagsValues, tagsArrays, datas); GetValues(schema, tagsValues, tagsArrays, datas);
@ -64,20 +66,22 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
var denormalized = await tagService.DenormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), tagsValues); var denormalized = await tagService.DenormalizeTagsAsync(appId, TagGroups.Schemas(schemaId), tagsValues);
foreach (var array in tagsArrays) foreach (var source in tagsArrays)
{ {
var array = source.AsArray;
for (var i = 0; i < array.Count; i++) for (var i = 0; i < array.Count; i++)
{ {
if (denormalized.TryGetValue(array[i].ToString(), out var result)) if (denormalized.TryGetValue(array[i].ToString(), out var result))
{ {
array[i] = JsonValue.Create(result); array[i] = result;
} }
} }
} }
} }
} }
private static void GetValues(Schema schema, HashSet<string> values, List<JsonArray> arrays, params ContentData[] datas) private static void GetValues(Schema schema, HashSet<string> values, List<JsonValue> arrays, params ContentData[] datas)
{ {
foreach (var field in schema.Fields) foreach (var field in schema.Fields)
{ {
@ -106,13 +110,13 @@ namespace Squidex.Domain.Apps.Core.Tags
{ {
foreach (var partition in fieldData) foreach (var partition in fieldData)
{ {
if (partition.Value is JsonArray array) if (partition.Value.Type == JsonValueType.Array)
{ {
foreach (var value in array) foreach (var value in partition.Value.AsArray)
{ {
if (value is JsonObject nestedObject) if (value.Type == JsonValueType.Object)
{ {
if (nestedObject.TryGetValue(nestedField.Name, out var nestedValue)) if (value.AsObject.TryGetValue(nestedField.Name, out var nestedValue))
{ {
ExtractTags(nestedValue, values, arrays); ExtractTags(nestedValue, values, arrays);
} }
@ -128,11 +132,11 @@ namespace Squidex.Domain.Apps.Core.Tags
} }
} }
private static void ExtractTags(IJsonValue value, ISet<string> values, ICollection<JsonArray> arrays) private static void ExtractTags(JsonValue value, ISet<string> values, ICollection<JsonValue> arrays)
{ {
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
foreach (var item in array) foreach (var item in value.AsArray)
{ {
if (item.Type == JsonValueType.String) if (item.Type == JsonValueType.String)
{ {
@ -140,7 +144,7 @@ namespace Squidex.Domain.Apps.Core.Tags
} }
} }
arrays.Add(array); arrays.Add(value);
} }
} }
} }

46
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/ContentFluidExtension.cs

@ -17,21 +17,51 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
{ {
public void RegisterGlobalTypes(IMemberAccessStrategy memberAccessStrategy) public void RegisterGlobalTypes(IMemberAccessStrategy memberAccessStrategy)
{ {
FluidValue.SetTypeMapping<ContentData>(x => new ObjectValue(x));
FluidValue.SetTypeMapping<ContentFieldData>(x => new ObjectValue(x));
FluidValue.SetTypeMapping<JsonObject>(x => new ObjectValue(x)); FluidValue.SetTypeMapping<JsonObject>(x => new ObjectValue(x));
FluidValue.SetTypeMapping<JsonArray>(x => new JsonArrayFluidValue(x)); FluidValue.SetTypeMapping<JsonArray>(x => new JsonArrayFluidValue(x));
FluidValue.SetTypeMapping<JsonString>(x => FluidValue.Create(x.Value));
FluidValue.SetTypeMapping<JsonBoolean>(x => FluidValue.Create(x.Value));
FluidValue.SetTypeMapping<JsonNumber>(x => FluidValue.Create(x.Value));
FluidValue.SetTypeMapping<JsonNull>(_ => FluidValue.Create(null));
memberAccessStrategy.Register<ContentData, object?>( FluidValue.SetTypeMapping<JsonValue>(source =>
(value, name) => value.GetOrDefault(name)); {
switch (source.Type)
{
case JsonValueType.Null:
return FluidValue.Create(null);
case JsonValueType.Boolean:
return FluidValue.Create(source.AsBoolean);
case JsonValueType.Number:
return FluidValue.Create(source.AsNumber);
case JsonValueType.String:
return FluidValue.Create(source.AsString);
case JsonValueType.Array:
return new JsonArrayFluidValue(source.AsArray);
case JsonValueType.Object:
return new ObjectValue(source.AsObject);
}
memberAccessStrategy.Register<JsonObject, object?>( ThrowHelper.InvalidOperationException();
return default!;
});
memberAccessStrategy.Register<JsonValue, object?>((value, name) =>
{
if (value.Type == JsonValueType.Object)
{
return value.AsObject.GetOrDefault(name);
}
return null;
});
memberAccessStrategy.Register<ContentData, object?>(
(value, name) => value.GetOrDefault(name)); (value, name) => value.GetOrDefault(name));
memberAccessStrategy.Register<ContentFieldData, object?>( memberAccessStrategy.Register<ContentFieldData, object?>(
(value, name) => value.GetOrDefault(name)); (value, name) => value.GetOrDefault(name).Value);
memberAccessStrategy.Register<JsonObject, object?>(
(value, name) => value.GetOrDefault(name).Value);
} }
} }
} }

4
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/JsonArrayFluidValue.cs

@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
public override string ToStringValue() public override string ToStringValue()
{ {
return value.ToString(); return value.ToString()!;
} }
protected override FluidValue GetValue(string name, TemplateContext context) protected override FluidValue GetValue(string name, TemplateContext context)
@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo) public override void WriteTo(TextWriter writer, TextEncoder encoder, CultureInfo cultureInfo)
{ {
writer.Write(value.ToString()); writer.Write(value);
} }
} }
} }

2
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs

@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return new AggregateValidator( return new AggregateValidator(
CreateFieldValidators(field) CreateFieldValidators(field)
.Union(Enumerable.Repeat( .Union(Enumerable.Repeat(
new ObjectValidator<IJsonValue>(partitioningValidators, isPartial, typeName), 1)), log); new ObjectValidator<JsonValue>(partitioningValidators, isPartial, typeName), 1)), log);
} }
private IValidator CreateValueValidator(IField field) private IValidator CreateValueValidator(IField field)

4
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs

@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
nestedValidators[nestedField.Name] = (false, args.Factory(nestedField)); nestedValidators[nestedField.Name] = (false, args.Factory(nestedField));
} }
yield return new CollectionItemValidator(new ObjectValidator<IJsonValue>(nestedValidators, false, "field")); yield return new CollectionItemValidator(new ObjectValidator<JsonValue>(nestedValidators, false, "field"));
} }
public IEnumerable<IValidator> Visit(IField<AssetsFieldProperties> field, Args args) public IEnumerable<IValidator> Visit(IField<AssetsFieldProperties> field, Args args)
@ -277,7 +277,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
nestedValidators[nestedField.Name] = (false, factory(nestedField)); nestedValidators[nestedField.Name] = (false, factory(nestedField));
} }
return new ObjectValidator<IJsonValue>(nestedValidators, false, "field"); return new ObjectValidator<JsonValue>(nestedValidators, false, "field");
}); });
} }
} }

111
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueConverter.cs

@ -22,13 +22,13 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
private static readonly JsonValueConverter Instance = new JsonValueConverter(); private static readonly JsonValueConverter Instance = new JsonValueConverter();
public record struct Args(IJsonValue Value, IJsonSerializer JsonSerializer, ResolvedComponents Components); public record struct Args(JsonValue Value, IJsonSerializer JsonSerializer, ResolvedComponents Components);
private JsonValueConverter() private JsonValueConverter()
{ {
} }
public static (object? Result, JsonError? Error) ConvertValue(IField field, IJsonValue value, IJsonSerializer jsonSerializer, public static (object? Result, JsonError? Error) ConvertValue(IField field, JsonValue value, IJsonSerializer jsonSerializer,
ResolvedComponents components) ResolvedComponents components)
{ {
Guard.NotNull(field); Guard.NotNull(field);
@ -76,9 +76,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public (object? Result, JsonError? Error) Visit(IField<BooleanFieldProperties> field, Args args) public (object? Result, JsonError? Error) Visit(IField<BooleanFieldProperties> field, Args args)
{ {
if (args.Value is JsonBoolean b) if (args.Value.Type == JsonValueType.Boolean)
{ {
return (b.Value, null); return (args.Value.AsBoolean, null);
} }
return (null, new JsonError(T.Get("contents.invalidBoolean"))); return (null, new JsonError(T.Get("contents.invalidBoolean")));
@ -86,9 +86,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public (object? Result, JsonError? Error) Visit(IField<NumberFieldProperties> field, Args args) public (object? Result, JsonError? Error) Visit(IField<NumberFieldProperties> field, Args args)
{ {
if (args.Value is JsonNumber n) if (args.Value.Type == JsonValueType.Number)
{ {
return (n.Value, null); return (args.Value.AsNumber, null);
} }
return (null, new JsonError(T.Get("contents.invalidNumber"))); return (null, new JsonError(T.Get("contents.invalidNumber")));
@ -96,9 +96,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public (object? Result, JsonError? Error) Visit(IField<StringFieldProperties> field, Args args) public (object? Result, JsonError? Error) Visit(IField<StringFieldProperties> field, Args args)
{ {
if (args.Value is JsonString s) if (args.Value.Type == JsonValueType.String)
{ {
return (s.Value, null); return (args.Value.AsString, null);
} }
return (null, new JsonError(T.Get("contents.invalidString"))); return (null, new JsonError(T.Get("contents.invalidString")));
@ -143,71 +143,84 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
} }
} }
private static (object? Result, JsonError? Error) ConvertToIdList(IJsonValue value) private static (object? Result, JsonError? Error) ConvertToIdList(JsonValue value)
{ {
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
var array = value.AsArray;
var result = new List<DomainId>(array.Count); var result = new List<DomainId>(array.Count);
for (var i = 0; i < array.Count; i++) foreach (var item in array)
{ {
if (array[i] is JsonString s && !string.IsNullOrWhiteSpace(s.Value)) if (item.Type == JsonValueType.String)
{ {
result.Add(DomainId.Create(s.Value)); var typed = item.AsString;
}
else if (!string.IsNullOrWhiteSpace(item.AsString))
{ {
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings"))); result.Add(DomainId.Create(typed));
continue;
} }
} }
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
}
return (result, null); return (result, null);
} }
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings"))); return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
} }
private static (object? Result, JsonError? Error) ConvertToStringList(IJsonValue value) private static (object? Result, JsonError? Error) ConvertToStringList(JsonValue value)
{ {
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
var array = value.AsArray;
var result = new List<string?>(array.Count); var result = new List<string?>(array.Count);
for (var i = 0; i < array.Count; i++) foreach (var item in array)
{ {
if (array[i] is JsonString s && !string.IsNullOrWhiteSpace(s.Value)) if (item.Type == JsonValueType.String)
{ {
result.Add(s.Value); var typed = item.AsString;
}
else if (!string.IsNullOrWhiteSpace(item.AsString))
{ {
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings"))); result.Add(typed);
continue;
} }
} }
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
}
return (result, null); return (result, null);
} }
return (null, new JsonError(T.Get("contents.invalidArrayOfStrings"))); return (null, new JsonError(T.Get("contents.invalidArrayOfStrings")));
} }
private static (object? Result, JsonError? Error) ConvertToObjectList(IJsonValue value) private static (object? Result, JsonError? Error) ConvertToObjectList(JsonValue value)
{ {
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
var array = value.AsArray;
var result = new List<JsonObject>(array.Count); var result = new List<JsonObject>(array.Count);
for (var i = 0; i < array.Count; i++) foreach (var item in array)
{ {
if (array[i] is JsonObject obj) if (item.Type == JsonValueType.Object)
{ {
result.Add(obj); result.Add(item.AsObject);
continue;
} }
else
{
return (null, new JsonError(T.Get("contents.invalidArrayOfObjects"))); return (null, new JsonError(T.Get("contents.invalidArrayOfObjects")));
} }
}
return (result, null); return (result, null);
} }
@ -215,25 +228,27 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidArrayOfObjects"))); return (null, new JsonError(T.Get("contents.invalidArrayOfObjects")));
} }
private static (object? Result, JsonError? Error) ConvertToComponentList(IJsonValue value, private static (object? Result, JsonError? Error) ConvertToComponentList(JsonValue value,
ResolvedComponents components, ReadonlyList<DomainId>? allowedIds) ResolvedComponents components, ReadonlyList<DomainId>? allowedIds)
{ {
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
var array = value.AsArray;
var result = new List<Component>(array.Count); var result = new List<Component>(array.Count);
for (var i = 0; i < array.Count; i++) foreach (var item in array)
{ {
var (item, error) = ConvertToComponent(array[i], components, allowedIds); var (component, error) = ConvertToComponent(item, components, allowedIds);
if (error != null) if (error != null)
{ {
return (null, error); return (null, error);
} }
if (item != null) if (component != null)
{ {
result.Add(item); result.Add(component);
} }
} }
@ -243,32 +258,34 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return (null, new JsonError(T.Get("contents.invalidArrayOfObjects"))); return (null, new JsonError(T.Get("contents.invalidArrayOfObjects")));
} }
private static (Component? Result, JsonError? Error) ConvertToComponent(IJsonValue value, private static (Component? Result, JsonError? Error) ConvertToComponent(JsonValue value,
ResolvedComponents components, ReadonlyList<DomainId>? allowedIds) ResolvedComponents components, ReadonlyList<DomainId>? allowedIds)
{ {
if (value is not JsonObject obj) if (value.Type != JsonValueType.Object)
{ {
return (null, new JsonError(T.Get("contents.invalidComponentNoObject"))); return (null, new JsonError(T.Get("contents.invalidComponentNoObject")));
} }
var id = default(DomainId); var id = DomainId.Empty;
var obj = value.AsObject;
if (obj.TryGetValue<JsonString>("schemaName", out var schemaName)) if (obj.TryGetValue("schemaName", out var schemaName) && schemaName.Type == JsonValueType.String)
{ {
id = components.FirstOrDefault(x => x.Value.Name == schemaName.Value).Key; id = components.FirstOrDefault(x => x.Value.Name == schemaName.AsString).Key;
obj.Remove("schemaName"); obj.Remove("schemaName");
obj[Component.Discriminator] = JsonValue.Create(id); obj[Component.Discriminator] = id;
} }
else if (obj.TryGetValue<JsonString>(Component.Discriminator, out var discriminator)) else if (obj.TryGetValue(Component.Discriminator, out var discriminator) && discriminator.Type == JsonValueType.String)
{ {
id = DomainId.Create(discriminator.Value); id = DomainId.Create(discriminator.AsString);
} }
else if (allowedIds?.Count == 1) else if (allowedIds?.Count == 1)
{ {
id = allowedIds[0]; id = allowedIds[0];
obj[Component.Discriminator] = JsonValue.Create(id); obj[Component.Discriminator] = id;
} }
if (id == default) if (id == default)

50
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/JsonValueValidator.cs

@ -20,13 +20,13 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
private static readonly JsonValueValidator Instance = new JsonValueValidator(); private static readonly JsonValueValidator Instance = new JsonValueValidator();
public record struct Args(IJsonValue Value, IJsonSerializer JsonSerializer); public record struct Args(JsonValue Value, IJsonSerializer JsonSerializer);
private JsonValueValidator() private JsonValueValidator()
{ {
} }
public static bool IsValid(IField field, IJsonValue value, IJsonSerializer jsonSerializer) public static bool IsValid(IField field, JsonValue value, IJsonSerializer jsonSerializer)
{ {
Guard.NotNull(field); Guard.NotNull(field);
Guard.NotNull(value); Guard.NotNull(value);
@ -48,7 +48,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public bool Visit(IField<BooleanFieldProperties> field, Args args) public bool Visit(IField<BooleanFieldProperties> field, Args args)
{ {
return args.Value is JsonBoolean; return args.Value.Type == JsonValueType.Boolean;
} }
public bool Visit(IField<ComponentFieldProperties> field, Args args) public bool Visit(IField<ComponentFieldProperties> field, Args args)
@ -87,7 +87,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public bool Visit(IField<NumberFieldProperties> field, Args args) public bool Visit(IField<NumberFieldProperties> field, Args args)
{ {
return args.Value is JsonNumber; return args.Value.Type == JsonValueType.Number;
} }
public bool Visit(IField<ReferencesFieldProperties> field, Args args) public bool Visit(IField<ReferencesFieldProperties> field, Args args)
@ -97,7 +97,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
public bool Visit(IField<StringFieldProperties> field, Args args) public bool Visit(IField<StringFieldProperties> field, Args args)
{ {
return args.Value is JsonString; return args.Value.Type == JsonValueType.String;
} }
public bool Visit(IField<TagsFieldProperties> field, Args args) public bool Visit(IField<TagsFieldProperties> field, Args args)
@ -110,13 +110,16 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return true; return true;
} }
private static bool IsValidStringList(IJsonValue value) private static bool IsValidStringList(JsonValue value)
{ {
if (value is JsonArray array) if (value.Type != JsonValueType.Array)
{ {
for (var i = 0; i < array.Count; i++) return false;
}
foreach (var item in value.AsArray)
{ {
if (array[i] is not JsonString) if (item.Type != JsonValueType.String)
{ {
return false; return false;
} }
@ -125,16 +128,16 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return true; return true;
} }
private static bool IsValidObjectList(JsonValue value)
{
if (value.Type != JsonValueType.Array)
{
return false; return false;
} }
private static bool IsValidObjectList(IJsonValue value) foreach (var item in value.AsArray)
{
if (value is JsonArray array)
{
for (var i = 0; i < array.Count; i++)
{ {
if (array[i] is not JsonObject) if (item.Type != JsonValueType.Object)
{ {
return false; return false;
} }
@ -143,16 +146,16 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return true; return true;
} }
private static bool IsValidComponentList(JsonValue value)
{
if (value.Type != JsonValueType.Array)
{
return false; return false;
} }
private static bool IsValidComponentList(IJsonValue value) foreach (var item in value.AsArray)
{
if (value is JsonArray array)
{
for (var i = 0; i < array.Count; i++)
{ {
if (!IsValidComponent(array[i])) if (!IsValidComponent(item))
{ {
return false; return false;
} }
@ -161,10 +164,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return true; return true;
} }
return false; private static bool IsValidComponent(JsonValue value)
}
private static bool IsValidComponent(IJsonValue value)
{ {
return Component.IsValid(value, out _); return Component.IsValid(value, out _);
} }

3
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System.Collections; using System.Collections;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
@ -20,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (minItems != null && minItems > maxItems) if (minItems != null && minItems > maxItems)
{ {
throw new ArgumentException("Min length must be greater than max length.", nameof(minItems)); ThrowHelper.ArgumentException("Min length must be greater than max length.", nameof(minItems));
} }
this.isRequired = isRequired; this.isRequired = isRequired;

4
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs

@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
try try
{ {
if (value is IJsonValue jsonValue) if (value is JsonValue jsonValue)
{ {
if (jsonValue.Type == JsonValueType.Null) if (jsonValue.Type == JsonValueType.Null)
{ {
@ -40,6 +40,8 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
} }
else else
{ {
typedValue = jsonValue.Value;
var (json, error) = JsonValueConverter.ConvertValue(field, jsonValue, context.JsonSerializer, context.Components); var (json, error) = JsonValueConverter.ConvertValue(field, jsonValue, context.JsonSerializer, context.Components);
if (error != null) if (error != null)

3
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
@ -18,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (min != null && max != null && min.Value.CompareTo(max.Value) > 0) if (min != null && max != null && min.Value.CompareTo(max.Value) > 0)
{ {
throw new ArgumentException("Min value must be greater than max value.", nameof(min)); ThrowHelper.ArgumentException("Min value must be greater than max value.", nameof(min));
} }
this.min = min; this.min = min;

3
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Translations;
namespace Squidex.Domain.Apps.Core.ValidateContent.Validators namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
@ -18,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (minLength > maxLength) if (minLength > maxLength)
{ {
throw new ArgumentException("Min length must be greater than max length.", nameof(minLength)); ThrowHelper.ArgumentException("Min length must be greater than max length.", nameof(minLength));
} }
this.minLength = minLength; this.minLength = minLength;

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringTextValidator.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Squidex.Infrastructure;
using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Translations;
using Squidex.Text; using Squidex.Text;
@ -26,12 +27,12 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (minCharacters > maxCharacters) if (minCharacters > maxCharacters)
{ {
throw new ArgumentException("Min characters must be greater than max characters.", nameof(minCharacters)); ThrowHelper.ArgumentException("Min characters must be greater than max characters.", nameof(minCharacters));
} }
if (minWords > maxWords) if (minWords > maxWords)
{ {
throw new ArgumentException("Min words must be greater than max words.", nameof(minWords)); ThrowHelper.ArgumentException("Min words must be greater than max words.", nameof(minWords));
} }
this.transform = transform; this.transform = transform;

2
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueObjectValuesValidator.cs

@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
private void Validate(ValidationContext context, AddError addError, IEnumerable<JsonObject> items) private void Validate(ValidationContext context, AddError addError, IEnumerable<JsonObject> items)
{ {
var duplicates = new HashSet<IJsonValue>(10); var duplicates = new HashSet<JsonValue>(10);
foreach (var field in fields) foreach (var field in fields)
{ {

7
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Text/LuceneQueryVisitor.cs

@ -11,6 +11,7 @@ using Lucene.Net.Index;
using Lucene.Net.Search; using Lucene.Net.Search;
using Lucene.Net.Util; using Lucene.Net.Util;
using MongoDB.Bson; using MongoDB.Bson;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.MongoDb.Text namespace Squidex.Domain.Apps.Entities.MongoDb.Text
{ {
@ -50,7 +51,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Text
case TermRangeQuery termRangeQuery: case TermRangeQuery termRangeQuery:
return VisitTermRange(termRangeQuery); return VisitTermRange(termRangeQuery);
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }
@ -59,7 +61,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Text
if (!TryParseValue(termRangeQuery.LowerTerm, out var min) || if (!TryParseValue(termRangeQuery.LowerTerm, out var min) ||
!TryParseValue(termRangeQuery.UpperTerm, out var max)) !TryParseValue(termRangeQuery.UpperTerm, out var max))
{ {
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
var minField = termRangeQuery.IncludesLower ? "gte" : "gt"; var minField = termRangeQuery.IncludesLower ? "gte" : "gt";

2
backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs

@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
return GetGrain(appId, userId).RemoveAsync(path); return GetGrain(appId, userId).RemoveAsync(path);
} }
public Task SetAsync(DomainId appId, string? userId, string path, IJsonValue value) public Task SetAsync(DomainId appId, string? userId, string path, JsonValue value)
{ {
return GetGrain(appId, userId).SetAsync(path, value.AsJ()); return GetGrain(appId, userId).SetAsync(path, value.AsJ());
} }

13
backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs

@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
[CollectionName("UISettings")] [CollectionName("UISettings")]
public sealed class State public sealed class State
{ {
public JsonObject Settings { get; set; } = JsonValue.Object(); public JsonObject Settings { get; set; } = new JsonObject();
} }
public AppUISettingsGrain(IGrainState<State> state) public AppUISettingsGrain(IGrainState<State> state)
@ -46,13 +46,14 @@ namespace Squidex.Domain.Apps.Entities.Apps
return state.WriteAsync(); return state.WriteAsync();
} }
public Task SetAsync(string path, J<IJsonValue> value) public Task SetAsync(string path, J<JsonValue> value)
{ {
var container = GetContainer(path, true, out var key); var container = GetContainer(path, true, out var key);
if (container == null) if (container == null)
{ {
throw new InvalidOperationException("Path does not lead to an object."); ThrowHelper.InvalidOperationException("Path does not lead to an object.");
return Task.CompletedTask;
} }
container[key] = value.Value; container[key] = value.Value;
@ -90,7 +91,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
if (add) if (add)
{ {
temp = JsonValue.Object(); temp = new JsonObject();
current[segment] = temp; current[segment] = temp;
} }
@ -100,9 +101,9 @@ namespace Squidex.Domain.Apps.Entities.Apps
} }
} }
if (temp is JsonObject next) if (temp.Type == JsonValueType.Object)
{ {
current = next; current = temp.AsObject;
} }
else else
{ {

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Commands/UpdateRole.cs

@ -15,6 +15,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Commands
public string[] Permissions { get; set; } public string[] Permissions { get; set; }
public JsonObject? Properties { get; set; } public JsonObject Properties { get; set; }
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs

@ -19,6 +19,8 @@ using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Shared.Users; using Squidex.Shared.Users;
#pragma warning disable MA0022 // Return Task.FromResult instead of returning null
namespace Squidex.Domain.Apps.Entities.Apps.DomainObject namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
{ {
public sealed partial class AppDomainObject : DomainObject<AppDomainObject.State> public sealed partial class AppDomainObject : DomainObject<AppDomainObject.State>
@ -297,7 +299,8 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
}); });
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettings.cs

@ -14,7 +14,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
Task<JsonObject> GetAsync(DomainId appId, string? userId); Task<JsonObject> GetAsync(DomainId appId, string? userId);
Task SetAsync(DomainId appId, string? userId, string path, IJsonValue value); Task SetAsync(DomainId appId, string? userId, string path, JsonValue value);
Task SetAsync(DomainId appId, string? userId, JsonObject settings); Task SetAsync(DomainId appId, string? userId, JsonObject settings);

2
backend/src/Squidex.Domain.Apps.Entities/Apps/IAppUISettingsGrain.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Apps
{ {
Task<J<JsonObject>> GetAsync(); Task<J<JsonObject>> GetAsync();
Task SetAsync(string path, J<IJsonValue> value); Task SetAsync(string path, J<JsonValue> value);
Task SetAsync(J<JsonObject> settings); Task SetAsync(J<JsonObject> settings);

5
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs

@ -16,6 +16,8 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
#pragma warning disable MA0022 // Return Task.FromResult instead of returning null
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
public sealed partial class AssetDomainObject : DomainObject<AssetDomainObject.State> public sealed partial class AssetDomainObject : DomainObject<AssetDomainObject.State>
@ -145,7 +147,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
await DeleteCore(c, operation); await DeleteCore(c, operation);
}); });
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

6
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetFolderDomainObject.cs

@ -10,11 +10,14 @@ using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards; using Squidex.Domain.Apps.Entities.Assets.DomainObject.Guards;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
#pragma warning disable MA0022 // Return Task.FromResult instead of returning null
namespace Squidex.Domain.Apps.Entities.Assets.DomainObject namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
{ {
public sealed partial class AssetFolderDomainObject : DomainObject<AssetFolderDomainObject.State> public sealed partial class AssetFolderDomainObject : DomainObject<AssetFolderDomainObject.State>
@ -80,7 +83,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
}); });
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

3
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetsBulkUpdateCommandMiddleware.cs

@ -201,7 +201,8 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
} }
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

11
backend/src/Squidex.Domain.Apps.Entities/Assets/FileTagAssetMetadataSource.cs

@ -9,7 +9,6 @@ using Squidex.Assets;
using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Assets;
using Squidex.Domain.Apps.Entities.Assets.Commands; using Squidex.Domain.Apps.Entities.Assets.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
using TagLib; using TagLib;
using TagLib.Image; using TagLib.Image;
using static TagLib.File; using static TagLib.File;
@ -34,7 +33,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
public Stream WriteStream public Stream WriteStream
{ {
get { throw new NotSupportedException(); } get => throw new NotSupportedException();
} }
public FileAbstraction(AssetFile file) public FileAbstraction(AssetFile file)
@ -87,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
if (!string.IsNullOrWhiteSpace(value)) if (!string.IsNullOrWhiteSpace(value))
{ {
command.Metadata.Add(name, JsonValue.Create(value)); command.Metadata.Add(name, value);
} }
} }
@ -95,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
if (value > 0) if (value > 0)
{ {
command.Metadata.Add(name, JsonValue.Create(value)); command.Metadata.Add(name, (double)value.Value);
} }
} }
@ -103,7 +102,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
if (value > 0) if (value > 0)
{ {
command.Metadata.Add(name, JsonValue.Create(value)); command.Metadata.Add(name, value.Value);
} }
} }
@ -111,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{ {
if (value != TimeSpan.Zero) if (value != TimeSpan.Zero)
{ {
command.Metadata.Add(name, JsonValue.Create(value.ToString())); command.Metadata.Add(name, value.ToString());
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities/Comments/DomainObject/CommentsGrain.cs

@ -14,6 +14,8 @@ using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
#pragma warning disable MA0022 // Return Task.FromResult instead of returning null
namespace Squidex.Domain.Apps.Entities.Comments.DomainObject namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
{ {
public sealed class CommentsGrain : GrainOfString, ICommentsGrain public sealed class CommentsGrain : GrainOfString, ICommentsGrain
@ -88,7 +90,8 @@ namespace Squidex.Domain.Apps.Entities.Comments.DomainObject
}); });
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

63
backend/src/Squidex.Domain.Apps.Entities/Contents/BackupContents.cs

@ -22,20 +22,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class BackupContents : IBackupHandler public sealed class BackupContents : IBackupHandler
{ {
private const int BatchSize = 100; private const int BatchSize = 100;
private delegate void ObjectSetter(IReadOnlyDictionary<string, IJsonValue> obj, string key, IJsonValue value);
private const string UrlsFile = "Urls.json"; private const string UrlsFile = "Urls.json";
private static readonly ObjectSetter JsonSetter = (obj, key, value) =>
{
((JsonObject)obj).Add(key, value);
};
private static readonly ObjectSetter FieldSetter = (obj, key, value) =>
{
((ContentFieldData)obj)[key] = value;
};
private readonly Dictionary<DomainId, HashSet<DomainId>> contentIdsBySchemaId = new Dictionary<DomainId, HashSet<DomainId>>(); private readonly Dictionary<DomainId, HashSet<DomainId>> contentIdsBySchemaId = new Dictionary<DomainId, HashSet<DomainId>>();
private readonly Rebuilder rebuilder; private readonly Rebuilder rebuilder;
private readonly IUrlGenerator urlGenerator; private readonly IUrlGenerator urlGenerator;
@ -109,26 +96,26 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
if (field != null) if (field != null)
{ {
ReplaceAssetUrl(field, FieldSetter); ReplaceAssetUrl(field);
} }
} }
} }
private void ReplaceAssetUrl(IReadOnlyDictionary<string, IJsonValue> source, ObjectSetter setter) private void ReplaceAssetUrl(IDictionary<string, JsonValue> source)
{ {
List<(string, string)>? replacements = null; List<(string, string)>? replacements = null;
foreach (var (key, value) in source) foreach (var (key, value) in source)
{ {
switch (value) switch (value.Type)
{ {
case JsonString s: case JsonValueType.String:
{ {
var newValue = s.Value; var oldValue = value.AsString;
newValue = newValue.Replace(assetsUrlOld!.AssetsApp, assetsUrlNew!.AssetsApp, StringComparison.Ordinal); var newValue = oldValue.Replace(assetsUrlOld!.AssetsApp, assetsUrlNew!.AssetsApp, StringComparison.Ordinal);
if (!ReferenceEquals(newValue, s.Value)) if (!ReferenceEquals(newValue, oldValue))
{ {
replacements ??= new List<(string, string)>(); replacements ??= new List<(string, string)>();
replacements.Add((key, newValue)); replacements.Add((key, newValue));
@ -137,7 +124,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
newValue = newValue.Replace(assetsUrlOld!.Assets, assetsUrlNew!.Assets, StringComparison.Ordinal); newValue = newValue.Replace(assetsUrlOld!.Assets, assetsUrlNew!.Assets, StringComparison.Ordinal);
if (!ReferenceEquals(newValue, s.Value)) if (!ReferenceEquals(newValue, oldValue))
{ {
replacements ??= new List<(string, string)>(); replacements ??= new List<(string, string)>();
replacements.Add((key, newValue)); replacements.Add((key, newValue));
@ -147,12 +134,12 @@ namespace Squidex.Domain.Apps.Entities.Contents
break; break;
case JsonArray arr: case JsonValueType.Array:
ReplaceAssetUrl(arr); ReplaceAssetUrl(value.AsArray);
break; break;
case JsonObject obj: case JsonValueType.Object:
ReplaceAssetUrl(obj, JsonSetter); ReplaceAssetUrl(value.AsObject);
break; break;
} }
} }
@ -161,47 +148,47 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
foreach (var (key, newValue) in replacements) foreach (var (key, newValue) in replacements)
{ {
setter(source, key, JsonValue.Create(newValue)); source[key] = newValue;
} }
} }
} }
private void ReplaceAssetUrl(JsonArray source) private void ReplaceAssetUrl(List<JsonValue> source)
{ {
for (var i = 0; i < source.Count; i++) for (var i = 0; i < source.Count; i++)
{ {
var value = source[i]; var value = source[i];
switch (value) switch (value.Type)
{ {
case JsonString s: case JsonValueType.String:
{ {
var newValue = s.Value; var oldValue = value.AsString;
newValue = newValue.Replace(assetsUrlOld!.AssetsApp, assetsUrlNew!.AssetsApp, StringComparison.Ordinal); var newValue = oldValue.Replace(assetsUrlOld!.AssetsApp, assetsUrlNew!.AssetsApp, StringComparison.Ordinal);
if (!ReferenceEquals(newValue, s.Value)) if (!ReferenceEquals(newValue, oldValue))
{ {
source[i] = JsonValue.Create(newValue); source[i] = newValue;
break; break;
} }
newValue = newValue.Replace(assetsUrlOld!.Assets, assetsUrlNew!.Assets, StringComparison.Ordinal); newValue = newValue.Replace(assetsUrlOld!.Assets, assetsUrlNew!.Assets, StringComparison.Ordinal);
if (!ReferenceEquals(newValue, s.Value)) if (!ReferenceEquals(newValue, oldValue))
{ {
source[i] = JsonValue.Create(newValue); source[i] = newValue;
break; break;
} }
} }
break; break;
case JsonArray: case JsonValueType.Array:
break; break;
case JsonObject obj: case JsonValueType.Object:
ReplaceAssetUrl(obj, JsonSetter); ReplaceAssetUrl(value.AsObject);
break; break;
} }
} }

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Commands/BulkUpdateJob.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Commands
{ {
public sealed class BulkUpdateJob public sealed class BulkUpdateJob
{ {
public Query<IJsonValue>? Query { get; set; } public Query<JsonValue>? Query { get; set; }
public DomainId? Id { get; set; } public DomainId? Id { get; set; }

4
backend/src/Squidex.Domain.Apps.Entities/Contents/ContentsSearchSource.cs

@ -95,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
IJsonValue? GetValue(ContentData? data, RootField field) JsonValue? GetValue(ContentData? data, RootField field)
{ {
if (data != null && data.TryGetValue(field.Name, out var fieldValue) && fieldValue != null) if (data != null && data.TryGetValue(field.Name, out var fieldValue) && fieldValue != null)
{ {
@ -121,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
var value = GetValue(content.ReferenceData, field) ?? GetValue(content.Data, field); var value = GetValue(content.ReferenceData, field) ?? GetValue(content.Data, field);
var formatted = StringFormatter.Format(field, value); var formatted = StringFormatter.Format(field, value ?? default);
if (!string.IsNullOrWhiteSpace(formatted)) if (!string.IsNullOrWhiteSpace(formatted))
{ {

5
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentDomainObject.cs

@ -20,6 +20,8 @@ using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
using Squidex.Shared; using Squidex.Shared;
#pragma warning disable MA0022 // Return Task.FromResult instead of returning null
namespace Squidex.Domain.Apps.Entities.Contents.DomainObject namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
{ {
public sealed partial class ContentDomainObject : DomainObject<ContentDomainObject.State> public sealed partial class ContentDomainObject : DomainObject<ContentDomainObject.State>
@ -220,7 +222,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
}); });
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

3
backend/src/Squidex.Domain.Apps.Entities/Contents/DomainObject/ContentsBulkUpdateCommandMiddleware.cs

@ -261,7 +261,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.DomainObject
} }
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

14
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return content; return content;
} }
public Task<IReadOnlyList<IEnrichedAssetEntity>> GetReferencedAssetsAsync(IJsonValue value, TimeSpan cacheDuration, public Task<IReadOnlyList<IEnrichedAssetEntity>> GetReferencedAssetsAsync(JsonValue value, TimeSpan cacheDuration,
CancellationToken ct) CancellationToken ct)
{ {
var ids = ParseIds(value); var ids = ParseIds(value);
@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return await LoadAsync(ids); return await LoadAsync(ids);
} }
public Task<IReadOnlyList<IEnrichedContentEntity>> GetReferencedContentsAsync(IJsonValue value, TimeSpan cacheDuration, public Task<IReadOnlyList<IEnrichedContentEntity>> GetReferencedContentsAsync(JsonValue value, TimeSpan cacheDuration,
CancellationToken ct) CancellationToken ct)
{ {
var ids = ParseIds(value); var ids = ParseIds(value);
@ -182,20 +182,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}); });
} }
private static List<DomainId>? ParseIds(IJsonValue value) private static List<DomainId>? ParseIds(JsonValue value)
{ {
try try
{ {
List<DomainId>? result = null; List<DomainId>? result = null;
if (value is JsonArray array) if (value.Type == JsonValueType.Array)
{ {
foreach (var id in array) foreach (var item in value.AsArray)
{ {
if (id is JsonString jsonString) if (item.Type == JsonValueType.String)
{ {
result ??= new List<DomainId>(); result ??= new List<DomainId>();
result.Add(DomainId.Create(jsonString.Value)); result.Add(DomainId.Create(item.AsString));
} }
} }
} }

7
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentGraphType.cs

@ -64,7 +64,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
return value => return value =>
{ {
return Component.IsValid(value as IJsonValue, out var discrimiator) && discrimiator == schemaId; if (value is not JsonObject json)
{
return false;
}
return Component.IsValid(json, out var discriminator) && discriminator == schemaId;
}; };
} }
} }

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ComponentUnionGraphType.cs

@ -46,9 +46,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
ResolveType = value => ResolveType = value =>
{ {
if (value is JsonObject component && component.TryGetValue<JsonString>(Component.Discriminator, out var schemaId)) if (value is JsonObject json && Component.IsValid(json, out var schemaId))
{ {
return types.GetOrDefault(schemaId.Value); return types.GetOrDefault(schemaId);
} }
return null; return null;

1
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/ContentActions.cs

@ -14,7 +14,6 @@ using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Translations;
using Squidex.Shared; using Squidex.Shared;

2
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/DataInputGraphType.cs

@ -78,7 +78,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
foreach (var item in list) foreach (var item in list)
{ {
if (item is JsonObject nested) if (item is JsonValue nested && nested.Type == JsonValueType.Object)
{ {
array.Add(nested); array.Add(nested);
} }

79
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/FieldVisitor.cs

@ -17,67 +17,84 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
{ {
public delegate T ValueResolver<T>(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context); public delegate T ValueResolver<T>(JsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context);
public delegate Task<T> AsyncValueResolver<T>(IJsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context); public delegate Task<T> AsyncValueResolver<T>(JsonValue value, IResolveFieldContext fieldContext, GraphQLExecutionContext context);
internal sealed class FieldVisitor : IFieldVisitor<FieldGraphSchema, FieldInfo> internal sealed class FieldVisitor : IFieldVisitor<FieldGraphSchema, FieldInfo>
{ {
public static readonly IFieldResolver JsonNoop = CreateValueResolver((value, fieldContext, contex) => value); public static readonly IFieldResolver JsonNoop = CreateValueResolver((value, fieldContext, contex) => value.Value);
public static readonly IFieldResolver JsonPath = CreateValueResolver(ContentActions.Json.Resolver); public static readonly IFieldResolver JsonPath = CreateValueResolver(ContentActions.Json.Resolver);
private static readonly IFieldResolver JsonBoolean = CreateValueResolver((value, fieldContext, contex) => private static readonly IFieldResolver JsonBoolean = CreateValueResolver((value, fieldContext, contex) =>
{ {
switch (value) switch (value.Type)
{ {
case JsonBoolean b: case JsonValueType.Boolean:
return b.Value; return value.AsBoolean;
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
}
});
private static readonly IFieldResolver JsonComponents = CreateValueResolver((value, fieldContext, contex) =>
{
switch (value.Type)
{
case JsonValueType.Array:
return value.AsArray.Select(x => x.AsObject).ToList();
default:
ThrowHelper.NotSupportedException();
return default!;
} }
}); });
private static readonly IFieldResolver JsonDateTime = CreateValueResolver((value, fieldContext, contex) => private static readonly IFieldResolver JsonDateTime = CreateValueResolver((value, fieldContext, contex) =>
{ {
switch (value) switch (value.Type)
{ {
case JsonString n: case JsonValueType.String:
return n.Value; return value.AsString;
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
}); });
private static readonly IFieldResolver JsonNumber = CreateValueResolver((value, fieldContext, contex) => private static readonly IFieldResolver JsonNumber = CreateValueResolver((value, fieldContext, contex) =>
{ {
switch (value) switch (value.Type)
{ {
case JsonNumber n: case JsonValueType.Number:
return n.Value; return value.AsNumber;
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
}); });
private static readonly IFieldResolver JsonString = CreateValueResolver((value, fieldContext, contex) => private static readonly IFieldResolver JsonString = CreateValueResolver((value, fieldContext, contex) =>
{ {
switch (value) switch (value.Type)
{ {
case JsonString s: case JsonValueType.String:
return s.Value; return value.AsString;
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
}); });
private static readonly IFieldResolver JsonStrings = CreateValueResolver((value, fieldContext, contex) => private static readonly IFieldResolver JsonStrings = CreateValueResolver((value, fieldContext, contex) =>
{ {
switch (value) switch (value.Type)
{ {
case JsonArray a: case JsonValueType.Array:
return a.Select(x => x.ToString()).ToList(); return value.AsArray.Select(x => x.ToString()).ToList();
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
}); });
@ -116,7 +133,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return default; return default;
} }
return new (new ListGraphType(new NonNullGraphType(type)), JsonNoop, null); return new (new ListGraphType(new NonNullGraphType(type)), JsonComponents, null);
} }
public FieldGraphSchema Visit(IField<AssetsFieldProperties> field, FieldInfo args) public FieldGraphSchema Visit(IField<AssetsFieldProperties> field, FieldInfo args)
@ -150,7 +167,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
return default; return default;
} }
return new (new ListGraphType(new NonNullGraphType(type)), JsonNoop, null); return new (new ListGraphType(new NonNullGraphType(type)), JsonComponents, null);
} }
public FieldGraphSchema Visit(IField<DateTimeFieldProperties> field, FieldInfo args) public FieldGraphSchema Visit(IField<DateTimeFieldProperties> field, FieldInfo args)
@ -243,7 +260,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
if (!union.HasType) if (!union.HasType)
{ {
return default; return null;
} }
contentType = union; contentType = union;
@ -267,7 +284,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
if (!union.HasType) if (!union.HasType)
{ {
return default; return null;
} }
componentType = union; componentType = union;
@ -278,13 +295,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
private static IFieldResolver CreateValueResolver<T>(ValueResolver<T> valueResolver) private static IFieldResolver CreateValueResolver<T>(ValueResolver<T> valueResolver)
{ {
return Resolvers.Sync<IReadOnlyDictionary<string, IJsonValue>, object?>((source, fieldContext, context) => return Resolvers.Sync<IReadOnlyDictionary<string, JsonValue>, object?>((source, fieldContext, context) =>
{ {
var key = fieldContext.FieldDefinition.SourceName(); var key = fieldContext.FieldDefinition.SourceName();
if (source.TryGetValue(key, out var value)) if (source.TryGetValue(key, out var value))
{ {
if (value is JsonNull) if (value == JsonValue.Null)
{ {
return null; return null;
} }
@ -298,13 +315,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
private static IFieldResolver CreateAsyncValueResolver<T>(AsyncValueResolver<T> valueResolver) private static IFieldResolver CreateAsyncValueResolver<T>(AsyncValueResolver<T> valueResolver)
{ {
return Resolvers.Async<IReadOnlyDictionary<string, IJsonValue>, object?>(async (source, fieldContext, context) => return Resolvers.Async<IReadOnlyDictionary<string, JsonValue>, object?>(async (source, fieldContext, context) =>
{ {
var key = fieldContext.FieldDefinition.SourceName(); var key = fieldContext.FieldDefinition.SourceName();
if (source.TryGetValue(key, out var value)) if (source.TryGetValue(key, out var value))
{ {
if (value is JsonNull) if (value == JsonValue.Null)
{ {
return null; return null;
} }

4
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Contents/NestedInputGraphType.cs

@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
public override object ParseDictionary(IDictionary<string, object?> value) public override object ParseDictionary(IDictionary<string, object?> value)
{ {
var result = JsonValue.Object(); var result = new JsonObject();
foreach (var field in Fields) foreach (var field in Fields)
{ {
@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents
} }
} }
return result; return new JsonValue(result);
} }
} }
} }

20
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonGraphType.cs

@ -23,28 +23,28 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
return ParseJson(value); return ParseJson(value);
} }
public static IJsonValue ParseJson(object? input) public static JsonValue ParseJson(object? input)
{ {
switch (input) switch (input)
{ {
case GraphQLBooleanValue booleanValue: case GraphQLBooleanValue booleanValue:
return JsonValue.Create(booleanValue.BoolValue); return booleanValue.BoolValue;
case GraphQLFloatValue floatValue: case GraphQLFloatValue floatValue:
return JsonValue.Create(double.Parse((string)floatValue.Value, NumberStyles.Integer, CultureInfo.InvariantCulture)); return double.Parse((string)floatValue.Value, NumberStyles.Integer, CultureInfo.InvariantCulture);
case GraphQLIntValue intValue: case GraphQLIntValue intValue:
return JsonValue.Create(int.Parse((string)intValue.Value, NumberStyles.Integer, CultureInfo.InvariantCulture)); return double.Parse((string)intValue.Value, NumberStyles.Integer, CultureInfo.InvariantCulture);
case GraphQLNullValue: case GraphQLNullValue:
return JsonValue.Null; return default;
case GraphQLStringValue stringValue: case GraphQLStringValue stringValue:
return JsonValue.Create((string)stringValue.Value); return (string)stringValue.Value;
case GraphQLListValue listValue: case GraphQLListValue listValue:
{ {
var json = JsonValue.Array(); var json = new JsonArray();
if (listValue.Values != null) if (listValue.Values != null)
{ {
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
case GraphQLObjectValue objectValue: case GraphQLObjectValue objectValue:
{ {
var json = JsonValue.Object(); var json = new JsonObject();
if (objectValue.Fields != null) if (objectValue.Fields != null)
{ {
@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
case IEnumerable<object> list: case IEnumerable<object> list:
{ {
var json = JsonValue.Array(); var json = new JsonArray();
foreach (var item in list) foreach (var item in list)
{ {
@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
case IDictionary<string, object> obj: case IDictionary<string, object> obj:
{ {
var json = JsonValue.Object(); var json = new JsonObject();
foreach (var (key, value) in obj) foreach (var (key, value) in obj)
{ {

5
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/Primitives/JsonValueNode.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using GraphQLParser;
using GraphQLParser.AST; using GraphQLParser.AST;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
@ -15,9 +14,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Primitives
{ {
public override ASTNodeKind Kind => ASTNodeKind.ObjectValue; public override ASTNodeKind Kind => ASTNodeKind.ObjectValue;
public IJsonValue Value { get; } public JsonValue Value { get; }
public JsonValueNode(IJsonValue value) public JsonValueNode(JsonValue value)
{ {
Value = value; Value = value;
} }

1
backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/Types/SharedTypes.cs

@ -7,7 +7,6 @@
using GraphQL.Types; using GraphQL.Types;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Assets; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Assets;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Contents;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Directives; using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Directives;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types namespace Squidex.Domain.Apps.Entities.Contents.GraphQL.Types

5
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs

@ -80,7 +80,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
if (schema == null) if (schema == null)
{ {
throw new InvalidOperationException(); ThrowHelper.InvalidOperationException();
return;
} }
var textQuery = new TextQuery(query.FullText, 1000) var textQuery = new TextQuery(query.FullText, 1000)
@ -168,7 +169,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
} }
} }
private ClrQuery ParseJson(Context context, ISchemaEntity? schema, Query<IJsonValue> query, private ClrQuery ParseJson(Context context, ISchemaEntity? schema, Query<JsonValue> query,
ResolvedComponents components) ResolvedComponents components)
{ {
var queryModel = BuildQueryModel(context, schema, components); var queryModel = BuildQueryModel(context, schema, components);

10
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveAssets.cs

@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
if (referencedAsset != null) if (referencedAsset != null)
{ {
IJsonValue array; var array = new JsonArray();
if (IsImage(referencedAsset)) if (IsImage(referencedAsset))
{ {
@ -89,13 +89,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
referencedAsset.AppId, referencedAsset.AppId,
referencedAsset.Id.ToString()); referencedAsset.Id.ToString());
array = JsonValue.Array(url, referencedAsset.FileName); array.Add(url);
}
else
{
array = JsonValue.Array(referencedAsset.FileName);
} }
array.Add(referencedAsset.FileName);
requestCache.AddDependency(referencedAsset.UniqueId, referencedAsset.Version); requestCache.AddDependency(referencedAsset.UniqueId, referencedAsset.Version);
fieldReference.AddLocalized(partitionKey, array); fieldReference.AddLocalized(partitionKey, array);

2
backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs

@ -125,7 +125,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps
{ {
var text = T.Get("contents.listReferences", new { count = referencedContents.Count }); var text = T.Get("contents.listReferences", new { count = referencedContents.Count });
var value = JsonValue.Object(); var value = new JsonObject();
foreach (var partitionKey in context.App.Languages.AllKeys) foreach (var partitionKey in context.App.Languages.AllKeys)
{ {

24
backend/src/Squidex.Domain.Apps.Entities/Contents/Text/Extensions.cs

@ -81,25 +81,27 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
return result; return result;
} }
private static void AppendJsonText(Dictionary<string, StringBuilder> languages, string language, IJsonValue value) private static void AppendJsonText(Dictionary<string, StringBuilder> languages, string language, JsonValue value)
{ {
if (value.Type == JsonValueType.String) switch (value.Type)
{ {
AppendText(languages, language, value.ToString()); case JsonValueType.String:
} AppendText(languages, language, value.AsString);
else if (value is JsonArray array) break;
{ case JsonValueType.Array:
foreach (var item in array) foreach (var item in value.AsArray)
{ {
AppendJsonText(languages, language, item); AppendJsonText(languages, language, item);
} }
}
else if (value is JsonObject obj) break;
{ case JsonValueType.Object:
foreach (var (_, item) in obj) foreach (var (_, item) in value.AsObject)
{ {
AppendJsonText(languages, language, item); AppendJsonText(languages, language, item);
} }
break;
} }
} }

4
backend/src/Squidex.Domain.Apps.Entities/Q.cs

@ -32,7 +32,7 @@ namespace Squidex.Domain.Apps.Entities
public Instant? ScheduledTo { get; init; } public Instant? ScheduledTo { get; init; }
public Query<IJsonValue>? JsonQuery { get; init; } public Query<JsonValue>? JsonQuery { get; init; }
public RefToken? CreatedBy { get; init; } public RefToken? CreatedBy { get; init; }
@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities
return this with { QueryAsJson = query }; return this with { QueryAsJson = query };
} }
public Q WithJsonQuery(Query<IJsonValue>? query) public Q WithJsonQuery(Query<JsonValue>? query)
{ {
return this with { JsonQuery = query }; return this with { JsonQuery = query };
} }

6
backend/src/Squidex.Domain.Apps.Entities/Rules/DomainObject/RuleDomainObject.cs

@ -10,11 +10,14 @@ using Squidex.Domain.Apps.Entities.Rules.Commands;
using Squidex.Domain.Apps.Entities.Rules.DomainObject.Guards; using Squidex.Domain.Apps.Entities.Rules.DomainObject.Guards;
using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Rules; using Squidex.Domain.Apps.Events.Rules;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
#pragma warning disable MA0022 // Return Task.FromResult instead of returning null
namespace Squidex.Domain.Apps.Entities.Rules.DomainObject namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
{ {
public sealed partial class RuleDomainObject : DomainObject<RuleDomainObject.State> public sealed partial class RuleDomainObject : DomainObject<RuleDomainObject.State>
@ -102,7 +105,8 @@ namespace Squidex.Domain.Apps.Entities.Rules.DomainObject
}); });
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

5
backend/src/Squidex.Domain.Apps.Entities/Schemas/DomainObject/SchemaDomainObject.cs

@ -19,6 +19,8 @@ using Squidex.Infrastructure.Orleans;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
#pragma warning disable MA0022 // Return Task.FromResult instead of returning null
namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
{ {
public sealed partial class SchemaDomainObject : DomainObject<SchemaDomainObject.State> public sealed partial class SchemaDomainObject : DomainObject<SchemaDomainObject.State>
@ -236,7 +238,8 @@ namespace Squidex.Domain.Apps.Entities.Schemas.DomainObject
}); });
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }

4
backend/src/Squidex.Domain.Users/DefaultKeyStore.cs

@ -10,6 +10,7 @@ using IdentityModel;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using OpenIddict.Server; using OpenIddict.Server;
using Squidex.Infrastructure;
using Squidex.Infrastructure.States; using Squidex.Infrastructure.States;
namespace Squidex.Domain.Users namespace Squidex.Domain.Users
@ -86,7 +87,8 @@ namespace Squidex.Domain.Users
if (state == null) if (state == null)
{ {
throw new InvalidOperationException("Cannot read key."); ThrowHelper.InvalidOperationException("Cannot read key.");
return default!;
} }
securityKey = new RsaSecurityKey(state.Parameters) securityKey = new RsaSecurityKey(state.Parameters)

3
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/BsonJsonReader.cs

@ -81,7 +81,8 @@ namespace Squidex.Infrastructure.MongoDb
SetToken(NewtonsoftJsonToken.Float, Decimal128.ToDouble(bsonReader.ReadDecimal128())); SetToken(NewtonsoftJsonToken.Float, Decimal128.ToDouble(bsonReader.ReadDecimal128()));
break; break;
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
break;
} }
} }
else if (bsonReader.State == BsonReaderState.EndOfDocument) else if (bsonReader.State == BsonReaderState.EndOfDocument)

6
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/DomainIdSerializer.cs

@ -49,7 +49,8 @@ namespace Squidex.Infrastructure.MongoDb
return DomainId.Create(binary.ToString()); return DomainId.Create(binary.ToString());
default: default:
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
} }
@ -62,7 +63,8 @@ namespace Squidex.Infrastructure.MongoDb
{ {
if (representation != BsonType.String) if (representation != BsonType.String)
{ {
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
return this; return this;

3
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/MongoRepositoryBase.cs

@ -38,7 +38,8 @@ namespace Squidex.Infrastructure.MongoDb
{ {
if (mongoCollection == null) if (mongoCollection == null)
{ {
throw new InvalidOperationException("Collection has not been initialized yet."); ThrowHelper.InvalidOperationException("Collection has not been initialized yet.");
return default!;
} }
return mongoCollection; return mongoCollection;

3
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs

@ -86,7 +86,8 @@ namespace Squidex.Infrastructure.MongoDb.Queries
return Filter.In(propertyName, ((IList)value!).OfType<object>()); return Filter.In(propertyName, ((IList)value!).OfType<object>());
} }
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default!;
} }
private static BsonRegularExpression BuildMatchRegex(CompareFilter<ClrValue> node) private static BsonRegularExpression BuildMatchRegex(CompareFilter<ClrValue> node)

126
backend/src/Squidex.Infrastructure/Collections/ListDictionary.KeyCollection.cs

@ -0,0 +1,126 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections;
namespace Squidex.Infrastructure.Collections
{
public partial class ListDictionary<TKey, TValue>
{
private sealed class KeyCollection : ICollection<TKey>
{
private readonly ListDictionary<TKey, TValue> dictionary;
public int Count
{
get => dictionary.Count;
}
public bool IsReadOnly
{
get => false;
}
public KeyCollection(ListDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
}
public void Add(TKey item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public void CopyTo(TKey[] array, int arrayIndex)
{
var i = 0;
foreach (var (key, _) in dictionary.entries)
{
array[arrayIndex + i] = key;
i++;
}
}
public bool Remove(TKey item)
{
throw new NotSupportedException();
}
public bool Contains(TKey item)
{
foreach (var entry in dictionary.entries)
{
if (dictionary.comparer.Equals(entry.Key, item))
{
return true;
}
}
return false;
}
public IEnumerator<TKey> GetEnumerator()
{
return new Enumerator(dictionary);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(dictionary);
}
private struct Enumerator : IEnumerator<TKey>, IEnumerator
{
private readonly ListDictionary<TKey, TValue> dictionary;
private int index = -1;
private TKey value = default!;
readonly TKey IEnumerator<TKey>.Current
{
get => value!;
}
readonly object IEnumerator.Current
{
get => value!;
}
public Enumerator(ListDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
}
public readonly void Dispose()
{
}
public bool MoveNext()
{
if (index >= dictionary.entries.Count - 1)
{
return false;
}
index++;
value = dictionary.entries[index].Key;
return true;
}
public void Reset()
{
index = -1;
}
}
}
}
}

126
backend/src/Squidex.Infrastructure/Collections/ListDictionary.ValueCollection.cs

@ -0,0 +1,126 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections;
namespace Squidex.Infrastructure.Collections
{
public partial class ListDictionary<TKey, TValue>
{
private sealed class ValueCollection : ICollection<TValue>
{
private readonly ListDictionary<TKey, TValue> dictionary;
public int Count
{
get => dictionary.Count;
}
public bool IsReadOnly
{
get => false;
}
public ValueCollection(ListDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
}
public void Add(TValue item)
{
throw new NotSupportedException();
}
public void Clear()
{
throw new NotSupportedException();
}
public void CopyTo(TValue[] array, int arrayIndex)
{
var i = 0;
foreach (var (_, value) in dictionary.entries)
{
array[arrayIndex + i] = value;
i++;
}
}
public bool Remove(TValue item)
{
throw new NotSupportedException();
}
public bool Contains(TValue item)
{
foreach (var entry in dictionary.entries)
{
if (Equals(entry.Value, item))
{
return true;
}
}
return false;
}
public IEnumerator<TValue> GetEnumerator()
{
return new Enumerator(dictionary);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(dictionary);
}
private struct Enumerator : IEnumerator<TValue>, IEnumerator
{
private readonly ListDictionary<TKey, TValue> dictionary;
private int index = -1;
private TValue value = default!;
readonly TValue IEnumerator<TValue>.Current
{
get => value!;
}
readonly object IEnumerator.Current
{
get => value!;
}
public Enumerator(ListDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
}
public readonly void Dispose()
{
}
public bool MoveNext()
{
if (index >= dictionary.entries.Count - 1)
{
return false;
}
index++;
value = dictionary.entries[index].Value;
return true;
}
public void Reset()
{
index = -1;
}
}
}
}
}

269
backend/src/Squidex.Infrastructure/Collections/ListDictionary.cs

@ -0,0 +1,269 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections;
using System.Diagnostics.CodeAnalysis;
namespace Squidex.Infrastructure.Collections
{
public partial class ListDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull
{
private readonly List<KeyValuePair<TKey, TValue>> entries = new List<KeyValuePair<TKey, TValue>>();
private readonly IEqualityComparer<TKey> comparer;
private struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IEnumerator
{
private readonly ListDictionary<TKey, TValue> dictionary;
private int index = -1;
private KeyValuePair<TKey, TValue> value = default!;
readonly KeyValuePair<TKey, TValue> IEnumerator<KeyValuePair<TKey, TValue>>.Current
{
get => value!;
}
readonly object IEnumerator.Current
{
get => value!;
}
public Enumerator(ListDictionary<TKey, TValue> dictionary)
{
this.dictionary = dictionary;
}
public readonly void Dispose()
{
}
public bool MoveNext()
{
if (index >= dictionary.entries.Count - 1)
{
return false;
}
index++;
value = dictionary.entries[index];
return true;
}
public void Reset()
{
index = -1;
}
}
public TValue this[TKey key]
{
get
{
if (!TryGetValue(key, out var result))
{
ThrowHelper.KeyNotFoundException();
return default!;
}
return result;
}
set
{
var index = -1;
for (var i = 0; i < entries.Count; i++)
{
if (comparer.Equals(entries[i].Key, key))
{
index = i;
break;
}
}
if (index >= 0)
{
entries[index] = new KeyValuePair<TKey, TValue>(key, value);
}
else
{
entries.Add(new KeyValuePair<TKey, TValue>(key, value));
}
}
}
public ICollection<TKey> Keys
{
get => new KeyCollection(this);
}
public ICollection<TValue> Values
{
get => new ValueCollection(this);
}
public int Count
{
get => entries.Count;
}
public int Capacity
{
get => entries.Capacity;
}
public bool IsReadOnly
{
get => false;
}
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
{
get => new KeyCollection(this);
}
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
{
get => new ValueCollection(this);
}
public ListDictionary()
: this(1, null)
{
}
public ListDictionary(ListDictionary<TKey, TValue> source, IEqualityComparer<TKey>? comparer = null)
{
Guard.NotNull(source);
entries = source.entries.ToList();
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
}
public ListDictionary(int capacity, IEqualityComparer<TKey>? comparer = null)
{
Guard.GreaterEquals(capacity, 0);
entries = new List<KeyValuePair<TKey, TValue>>(capacity);
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
}
public void Add(TKey key, TValue value)
{
if (ContainsKey(key))
{
ThrowHelper.ArgumentException("Key already exists.", nameof(key));
}
entries.Add(new KeyValuePair<TKey, TValue>(key, value));
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Clear()
{
entries.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
foreach (var entry in entries)
{
if (comparer.Equals(entry.Key, item.Key) && Equals(entry.Value, item.Value))
{
return true;
}
}
return false;
}
public bool ContainsKey(TKey key)
{
foreach (var entry in entries)
{
if (comparer.Equals(entry.Key, key))
{
return true;
}
}
return false;
}
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
foreach (var entry in entries)
{
if (comparer.Equals(entry.Key, key))
{
value = entry.Value;
return true;
}
}
value = default;
return false;
}
public bool Remove(TKey key)
{
for (var i = 0; i < entries.Count; i++)
{
var entry = entries[i];
if (comparer.Equals(entry.Key, key))
{
entries.RemoveAt(i);
return true;
}
}
return false;
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
for (var i = 0; i < entries.Count; i++)
{
var entry = entries[i];
if (comparer.Equals(entry.Key, item.Key) && Equals(entry.Value, item.Value))
{
entries.RemoveAt(i);
return true;
}
}
return false;
}
public void TrimExcess()
{
entries.TrimExcess();
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
entries.CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return new Enumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(this);
}
}
}

2
backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs

@ -165,7 +165,7 @@ namespace Squidex.Infrastructure.Commands
if (errorRate > errorThreshold) if (errorRate > errorThreshold)
{ {
throw new InvalidOperationException($"Error rate of {errorRate} is above threshold {errorThreshold}."); ThrowHelper.InvalidOperationException($"Error rate of {errorRate} is above threshold {errorThreshold}.");
} }
} }

2
backend/src/Squidex.Infrastructure/Diagnostics/Diagnoser.cs

@ -125,7 +125,7 @@ namespace Squidex.Infrastructure.Diagnostics
if (process.ExitCode != 0) if (process.ExitCode != 0)
{ {
throw new InvalidOperationException($"Failed to execute tool. Got exit code: {process.ExitCode}."); ThrowHelper.InvalidOperationException($"Failed to execute tool. Got exit code: {process.ExitCode}.");
} }
await using (var fs = new FileStream(writtenFile, FileMode.Open)) await using (var fs = new FileStream(writtenFile, FileMode.Open))

28
backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeExtensions.cs

@ -21,7 +21,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Envelope<T> SetEventPosition<T>(this Envelope<T> envelope, string value) where T : class, IEvent public static Envelope<T> SetEventPosition<T>(this Envelope<T> envelope, string value) where T : class, IEvent
{ {
envelope.Headers[CommonHeaders.EventNumber] = JsonValue.Create(value); envelope.Headers[CommonHeaders.EventNumber] = value;
return envelope; return envelope;
} }
@ -33,7 +33,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Envelope<T> SetEventStreamNumber<T>(this Envelope<T> envelope, long value) where T : class, IEvent public static Envelope<T> SetEventStreamNumber<T>(this Envelope<T> envelope, long value) where T : class, IEvent
{ {
envelope.Headers[CommonHeaders.EventStreamNumber] = JsonValue.Create(value); envelope.Headers[CommonHeaders.EventStreamNumber] = (double)value;
return envelope; return envelope;
} }
@ -45,7 +45,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Envelope<T> SetCommitId<T>(this Envelope<T> envelope, Guid value) where T : class, IEvent public static Envelope<T> SetCommitId<T>(this Envelope<T> envelope, Guid value) where T : class, IEvent
{ {
envelope.Headers[CommonHeaders.CommitId] = JsonValue.Create(value); envelope.Headers[CommonHeaders.CommitId] = value.ToString();
return envelope; return envelope;
} }
@ -57,7 +57,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Envelope<T> SetAggregateId<T>(this Envelope<T> envelope, DomainId value) where T : class, IEvent public static Envelope<T> SetAggregateId<T>(this Envelope<T> envelope, DomainId value) where T : class, IEvent
{ {
envelope.Headers[CommonHeaders.AggregateId] = JsonValue.Create(value); envelope.Headers[CommonHeaders.AggregateId] = value;
return envelope; return envelope;
} }
@ -69,7 +69,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Envelope<T> SetEventId<T>(this Envelope<T> envelope, Guid value) where T : class, IEvent public static Envelope<T> SetEventId<T>(this Envelope<T> envelope, Guid value) where T : class, IEvent
{ {
envelope.Headers[CommonHeaders.EventId] = JsonValue.Create(value); envelope.Headers[CommonHeaders.EventId] = value.ToString();
return envelope; return envelope;
} }
@ -81,7 +81,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Envelope<T> SetTimestamp<T>(this Envelope<T> envelope, Instant value) where T : class, IEvent public static Envelope<T> SetTimestamp<T>(this Envelope<T> envelope, Instant value) where T : class, IEvent
{ {
envelope.Headers[CommonHeaders.Timestamp] = JsonValue.Create(value); envelope.Headers[CommonHeaders.Timestamp] = value;
return envelope; return envelope;
} }
@ -93,7 +93,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Envelope<T> SetRestored<T>(this Envelope<T> envelope, bool value = true) where T : class, IEvent public static Envelope<T> SetRestored<T>(this Envelope<T> envelope, bool value = true) where T : class, IEvent
{ {
envelope.Headers[CommonHeaders.Restored] = JsonValue.Create(value); envelope.Headers[CommonHeaders.Restored] = value;
return envelope; return envelope;
} }
@ -102,11 +102,11 @@ namespace Squidex.Infrastructure.EventSourcing
{ {
if (obj.TryGetValue(key, out var v)) if (obj.TryGetValue(key, out var v))
{ {
if (v is JsonNumber number) if (v.Type == JsonValueType.Number)
{ {
return (long)number.Value; return (long)v.AsNumber;
} }
else if (v.Type == JsonValueType.String && double.TryParse(v.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) else if (v.Type == JsonValueType.String && double.TryParse(v.AsString, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
{ {
return (long)result; return (long)result;
} }
@ -117,7 +117,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Guid GetGuid(this EnvelopeHeaders obj, string key) public static Guid GetGuid(this EnvelopeHeaders obj, string key)
{ {
if (obj.TryGetValue(key, out var v) && v is JsonString s && Guid.TryParse(s.ToString(), out var guid)) if (obj.TryGetValue(key, out var v) && v.Type == JsonValueType.String && Guid.TryParse(v.AsString, out var guid))
{ {
return guid; return guid;
} }
@ -127,7 +127,7 @@ namespace Squidex.Infrastructure.EventSourcing
public static Instant GetInstant(this EnvelopeHeaders obj, string key) public static Instant GetInstant(this EnvelopeHeaders obj, string key)
{ {
if (obj.TryGetValue(key, out var v) && v is JsonString s && InstantPattern.ExtendedIso.Parse(s.ToString()).TryGetValue(default, out var instant)) if (obj.TryGetValue(key, out var v) && v.Type == JsonValueType.String && InstantPattern.ExtendedIso.Parse(v.AsString).TryGetValue(default, out var instant))
{ {
return instant; return instant;
} }
@ -147,9 +147,9 @@ namespace Squidex.Infrastructure.EventSourcing
public static bool GetBoolean(this EnvelopeHeaders obj, string key) public static bool GetBoolean(this EnvelopeHeaders obj, string key)
{ {
if (obj.TryGetValue(key, out var v) && v is JsonBoolean boolean) if (obj.TryGetValue(key, out var v) && v.Type == JsonValueType.Boolean)
{ {
return boolean.Value; return v.AsBoolean;
} }
return false; return false;

4
backend/src/Squidex.Infrastructure/EventSourcing/EnvelopeHeaders.cs

@ -9,13 +9,13 @@ using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.EventSourcing namespace Squidex.Infrastructure.EventSourcing
{ {
public sealed class EnvelopeHeaders : Dictionary<string, IJsonValue> public sealed class EnvelopeHeaders : Dictionary<string, JsonValue>
{ {
public EnvelopeHeaders() public EnvelopeHeaders()
{ {
} }
public EnvelopeHeaders(IDictionary<string, IJsonValue> headers) public EnvelopeHeaders(IDictionary<string, JsonValue> headers)
: base(headers) : base(headers)
{ {
} }

60
backend/src/Squidex.Infrastructure/Guard.cs

@ -21,7 +21,8 @@ namespace Squidex.Infrastructure
{ {
if (float.IsNaN(target) || float.IsPositiveInfinity(target) || float.IsNegativeInfinity(target)) if (float.IsNaN(target) || float.IsPositiveInfinity(target) || float.IsNegativeInfinity(target))
{ {
throw new ArgumentException("Value must be a valid number.", parameterName); ThrowHelper.ArgumentException("Value must be a valid number.", parameterName);
return default!;
} }
return target; return target;
@ -34,7 +35,8 @@ namespace Squidex.Infrastructure
{ {
if (double.IsNaN(target) || double.IsPositiveInfinity(target) || double.IsNegativeInfinity(target)) if (double.IsNaN(target) || double.IsPositiveInfinity(target) || double.IsNegativeInfinity(target))
{ {
throw new ArgumentException("Value must be a valid number.", parameterName); ThrowHelper.ArgumentException("Value must be a valid number.", parameterName);
return default!;
} }
return target; return target;
@ -49,7 +51,8 @@ namespace Squidex.Infrastructure
if (!target!.IsSlug()) if (!target!.IsSlug())
{ {
throw new ArgumentException("Target is not a valid slug.", parameterName); ThrowHelper.ArgumentException("Target is not a valid slug.", parameterName);
return default!;
} }
return target!; return target!;
@ -64,7 +67,8 @@ namespace Squidex.Infrastructure
if (!target!.IsPropertyName()) if (!target!.IsPropertyName())
{ {
throw new ArgumentException("Target is not a valid property name.", parameterName); ThrowHelper.ArgumentException("Target is not a valid property name.", parameterName);
return default!;
} }
return target!; return target!;
@ -77,7 +81,8 @@ namespace Squidex.Infrastructure
{ {
if (target != null && target.GetType() != typeof(T)) if (target != null && target.GetType() != typeof(T))
{ {
throw new ArgumentException($"The parameter must be of type {typeof(T)}", parameterName); ThrowHelper.ArgumentException($"The parameter must be of type {typeof(T)}", parameterName);
return default!;
} }
return target; return target;
@ -90,7 +95,8 @@ namespace Squidex.Infrastructure
{ {
if (target != null && expectedType != null && target.GetType() != expectedType) if (target != null && expectedType != null && target.GetType() != expectedType)
{ {
throw new ArgumentException($"The parameter must be of type {expectedType}", parameterName); ThrowHelper.ArgumentException($"The parameter must be of type {expectedType}", parameterName);
return default!;
} }
return target; return target;
@ -103,7 +109,8 @@ namespace Squidex.Infrastructure
{ {
if (!target.IsBetween(lower, upper)) if (!target.IsBetween(lower, upper))
{ {
throw new ArgumentException($"Value must be between {lower} and {upper}", parameterName); ThrowHelper.ArgumentException($"Value must be between {lower} and {upper}", parameterName);
return default!;
} }
return target; return target;
@ -116,7 +123,8 @@ namespace Squidex.Infrastructure
{ {
if (!target.IsEnumValue()) if (!target.IsEnumValue())
{ {
throw new ArgumentException($"Value must be a valid enum type {typeof(TEnum)}", parameterName); ThrowHelper.ArgumentException($"Value must be a valid enum type {typeof(TEnum)}", parameterName);
return default!;
} }
return target; return target;
@ -129,7 +137,8 @@ namespace Squidex.Infrastructure
{ {
if (target.CompareTo(lower) <= 0) if (target.CompareTo(lower) <= 0)
{ {
throw new ArgumentException($"Value must be greater than {lower}", parameterName); ThrowHelper.ArgumentException($"Value must be greater than {lower}", parameterName);
return default!;
} }
return target; return target;
@ -142,7 +151,8 @@ namespace Squidex.Infrastructure
{ {
if (target.CompareTo(lower) < 0) if (target.CompareTo(lower) < 0)
{ {
throw new ArgumentException($"Value must be greater or equal to {lower}", parameterName); ThrowHelper.ArgumentException($"Value must be greater or equal to {lower}", parameterName);
return default!;
} }
return target; return target;
@ -155,7 +165,8 @@ namespace Squidex.Infrastructure
{ {
if (target.CompareTo(upper) >= 0) if (target.CompareTo(upper) >= 0)
{ {
throw new ArgumentException($"Value must be less than {upper}", parameterName); ThrowHelper.ArgumentException($"Value must be less than {upper}", parameterName);
return default!;
} }
return target; return target;
@ -168,7 +179,8 @@ namespace Squidex.Infrastructure
{ {
if (target.CompareTo(upper) > 0) if (target.CompareTo(upper) > 0)
{ {
throw new ArgumentException($"Value must be less or equal to {upper}", parameterName); ThrowHelper.ArgumentException($"Value must be less or equal to {upper}", parameterName);
return default!;
} }
return target; return target;
@ -183,7 +195,8 @@ namespace Squidex.Infrastructure
if (target != null && target.Count == 0) if (target != null && target.Count == 0)
{ {
throw new ArgumentException("Collection does not contain an item.", parameterName); ThrowHelper.ArgumentException("Collection does not contain an item.", parameterName);
return default!;
} }
return target!; return target!;
@ -196,7 +209,8 @@ namespace Squidex.Infrastructure
{ {
if (target == Guid.Empty) if (target == Guid.Empty)
{ {
throw new ArgumentException("Value cannot be empty.", parameterName); ThrowHelper.ArgumentException("Value cannot be empty.", parameterName);
return default!;
} }
return target; return target;
@ -209,7 +223,8 @@ namespace Squidex.Infrastructure
{ {
if (target == DomainId.Empty) if (target == DomainId.Empty)
{ {
throw new ArgumentException("Value cannot be empty.", parameterName); ThrowHelper.ArgumentException("Value cannot be empty.", parameterName);
return default!;
} }
return target; return target;
@ -222,7 +237,8 @@ namespace Squidex.Infrastructure
{ {
if (target == null) if (target == null)
{ {
throw new ArgumentNullException(parameterName); ThrowHelper.ArgumentNullException(parameterName);
return default!;
} }
return target; return target;
@ -235,7 +251,8 @@ namespace Squidex.Infrastructure
{ {
if (target == null) if (target == null)
{ {
throw new ArgumentNullException(parameterName); ThrowHelper.ArgumentNullException(parameterName);
return default!;
} }
return target; return target;
@ -248,7 +265,8 @@ namespace Squidex.Infrastructure
{ {
if (Equals(target, default(TValue)!)) if (Equals(target, default(TValue)!))
{ {
throw new ArgumentException("Value cannot be an the default value.", parameterName); ThrowHelper.ArgumentException("Value cannot be an the default value.", parameterName);
return default!;
} }
return target; return target;
@ -263,7 +281,8 @@ namespace Squidex.Infrastructure
if (string.IsNullOrWhiteSpace(target)) if (string.IsNullOrWhiteSpace(target))
{ {
throw new ArgumentException("String parameter cannot be null or empty and cannot contain only blanks.", parameterName); ThrowHelper.ArgumentException("String parameter cannot be null or empty and cannot contain only blanks.", parameterName);
return default!;
} }
return target; return target;
@ -278,7 +297,8 @@ namespace Squidex.Infrastructure
if (target != null && target.Intersect(Path.GetInvalidFileNameChars()).Any()) if (target != null && target.Intersect(Path.GetInvalidFileNameChars()).Any())
{ {
throw new ArgumentException("Value contains an invalid character.", parameterName); ThrowHelper.ArgumentException("Value contains an invalid character.", parameterName);
return default!;
} }
return target!; return target!;

4
backend/src/Squidex.Infrastructure/Json/JsonException.cs

@ -16,12 +16,12 @@ namespace Squidex.Infrastructure.Json
{ {
} }
public JsonException(string message) public JsonException(string? message)
: base(message) : base(message)
{ {
} }
public JsonException(string message, Exception inner) public JsonException(string? message, Exception? inner)
: base(message, inner) : base(message, inner)
{ {
} }

70
backend/src/Squidex.Infrastructure/Json/Newtonsoft/JsonValueConverter.cs

@ -16,13 +16,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
{ {
private readonly HashSet<Type> supportedTypes = new HashSet<Type> private readonly HashSet<Type> supportedTypes = new HashSet<Type>
{ {
typeof(IJsonValue), typeof(JsonValue)
typeof(JsonArray),
typeof(JsonBoolean),
typeof(JsonNull),
typeof(JsonNumber),
typeof(JsonObject),
typeof(JsonString)
}; };
public virtual IEnumerable<Type> SupportedTypes public virtual IEnumerable<Type> SupportedTypes
@ -35,7 +29,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
return ReadJson(reader); return ReadJson(reader);
} }
private static IJsonValue ReadJson(JsonReader reader) private static JsonValue ReadJson(JsonReader reader)
{ {
switch (reader.TokenType) switch (reader.TokenType)
{ {
@ -63,6 +57,8 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
result[propertyName] = value; result[propertyName] = value;
break; break;
case JsonToken.EndObject: case JsonToken.EndObject:
result.TrimExcess();
return result; return result;
} }
} }
@ -81,6 +77,8 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
case JsonToken.Comment: case JsonToken.Comment:
continue; continue;
case JsonToken.EndArray: case JsonToken.EndArray:
result.TrimExcess();
return result; return result;
default: default:
var value = ReadJson(reader); var value = ReadJson(reader);
@ -94,25 +92,27 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
} }
case JsonToken.Integer when reader.Value is int i: case JsonToken.Integer when reader.Value is int i:
return JsonValue.Create(i); return i;
case JsonToken.Integer when reader.Value is long l: case JsonToken.Integer when reader.Value is long l:
return JsonValue.Create(l); return l;
case JsonToken.Float when reader.Value is float f: case JsonToken.Float when reader.Value is float f:
return JsonValue.Create(f); return f;
case JsonToken.Float when reader.Value is double d: case JsonToken.Float when reader.Value is double d:
return JsonValue.Create(d); return d;
case JsonToken.Boolean when reader.Value is bool b: case JsonToken.Boolean when reader.Value is bool b:
return JsonValue.Create(b); return b;
case JsonToken.Date when reader.Value is DateTime d: case JsonToken.Date when reader.Value is DateTime d:
return JsonValue.Create(d.ToIso8601()); return d.ToIso8601();
case JsonToken.String when reader.Value is string s: case JsonToken.String when reader.Value is string s:
return JsonValue.Create(s); return s;
case JsonToken.Null: case JsonToken.Null:
return default;
case JsonToken.Undefined: case JsonToken.Undefined:
return JsonValue.Null; return default;
} }
throw new NotSupportedException(); ThrowHelper.NotSupportedException();
return default;
} }
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
@ -123,48 +123,50 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
return; return;
} }
WriteJson(writer, (IJsonValue)value); WriteJson(writer, (JsonValue)value);
} }
private static void WriteJson(JsonWriter writer, IJsonValue value) private static void WriteJson(JsonWriter writer, JsonValue value)
{ {
switch (value) switch (value.Type)
{ {
case JsonNull: case JsonValueType.Null:
writer.WriteNull(); writer.WriteNull();
break; break;
case JsonBoolean s: case JsonValueType.Boolean:
writer.WriteValue(s.Value); writer.WriteValue(value.AsBoolean);
break; break;
case JsonString s: case JsonValueType.String:
writer.WriteValue(s.Value); writer.WriteValue(value.AsString);
break; break;
case JsonNumber s: case JsonValueType.Number:
if (s.Value % 1 == 0) var number = value.AsNumber;
if (number % 1 == 0)
{ {
writer.WriteValue((long)s.Value); writer.WriteValue((long)number);
} }
else else
{ {
writer.WriteValue(s.Value); writer.WriteValue(number);
} }
break; break;
case JsonArray array: case JsonValueType.Array:
writer.WriteStartArray(); writer.WriteStartArray();
for (var i = 0; i < array.Count; i++) foreach (var item in value.AsArray)
{ {
WriteJson(writer, array[i]); WriteJson(writer, item);
} }
writer.WriteEndArray(); writer.WriteEndArray();
break; break;
case JsonObject obj: case JsonValueType.Object:
writer.WriteStartObject(); writer.WriteStartObject();
foreach (var (key, jsonValue) in obj) foreach (var (key, jsonValue) in value.AsObject)
{ {
writer.WritePropertyName(key); writer.WritePropertyName(key);

8
backend/src/Squidex.Infrastructure/Json/Newtonsoft/NewtonsoftJsonSerializer.cs

@ -44,7 +44,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
} }
catch (NewtonsoftException ex) catch (NewtonsoftException ex)
{ {
throw new JsonException(ex.Message, ex); ThrowHelper.JsonException(ex.Message, ex);
} }
} }
@ -64,7 +64,8 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
} }
catch (NewtonsoftException ex) catch (NewtonsoftException ex)
{ {
throw new JsonException(ex.Message, ex); ThrowHelper.JsonException(ex.Message, ex);
return default!;
} }
} }
@ -84,7 +85,8 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
} }
catch (NewtonsoftException ex) catch (NewtonsoftException ex)
{ {
throw new JsonException(ex.Message, ex); ThrowHelper.JsonException(ex.Message, ex);
return default!;
} }
} }

3
backend/src/Squidex.Infrastructure/Json/Newtonsoft/TypeConverterJsonConverter.cs

@ -32,7 +32,8 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new JsonException("Error while converting from string.", ex); ThrowHelper.JsonException("Error while converting from string.", ex);
return default;
} }
} }

24
backend/src/Squidex.Infrastructure/Json/Objects/IJsonValue.cs

@ -1,24 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Diagnostics.CodeAnalysis;
namespace Squidex.Infrastructure.Json.Objects
{
public interface IJsonValue : IEquatable<IJsonValue>
{
JsonValueType Type { get; }
bool TryGet(string pathSegment, [MaybeNullWhen(false)] out IJsonValue result);
IJsonValue Clone();
string ToJsonString();
string ToString();
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save