public void SetIntegerExtensionSetImplicitlyWithString_Updates() { var cloudEvent = new CloudEvent(); cloudEvent["ext"] = "10"; Assert.Equal(CloudEventAttributeType.String, cloudEvent.GetAttribute("ext").Type); var attr = CloudEventAttribute.CreateExtension("ext", CloudEventAttributeType.Integer); // Setting the event with the attribute updates the extension registry... cloudEvent[attr] = 10; Assert.Equal(attr, cloudEvent.GetAttribute("ext")); // So we can fetch the value by string or attribute. Assert.Equal(10, cloudEvent[attr]); Assert.Equal(10, cloudEvent["ext"]); }
public void SetAttributeFromStringValue_NewAttribute() { var cloudEvent = new CloudEvent(); cloudEvent.SetAttributeFromString("ext", "text"); Assert.Equal("text", cloudEvent["ext"]); Assert.Equal(CloudEventAttributeType.String, cloudEvent.GetAttribute("ext").Type); }
public void ClearNewExtensionAttributeRetainsAttributeType() { var cloudEvent = new CloudEvent(); var attr = CloudEventAttribute.CreateExtension("ext", CloudEventAttributeType.Integer); cloudEvent[attr] = null; // Doesn't set any value, but remembers the extension... cloudEvent["ext"] = 10; // Which means it can be set as the integer later. Assert.Same(attr, cloudEvent.GetAttribute("ext")); }
/// <summary> /// Attempts to get the specified attribute /// </summary> /// <param name="e">The <see cref="CloudEvent"/> to check</param> /// <param name="name">The name of the attribute to get</param> /// <param name="value">The value of the attribute, if any</param> /// <returns>A boolean indicating whether or not the specified attribute is defined in the specified <see cref="CloudEvent"/></returns> public static bool TryGetAttribute(this CloudEvent e, string name, out string value) { value = null !; if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException(nameof(name)); } var attribute = e.GetAttribute(name); if (attribute == null) { return(false); } value = attribute.Format(e[attribute]); return(true); }
private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JsonElement element) { foreach (var jsonProperty in element.EnumerateObject()) { var name = jsonProperty.Name; var value = jsonProperty.Value; // Skip the spec version attribute, which we've already taken account of. // Data is handled later, when everything else (importantly, the data content type) // has been populated. if (name == CloudEventsSpecVersion.SpecVersionAttribute.Name || name == DataBase64PropertyName || name == DataPropertyName) { continue; } // For non-extension attributes, validate that the token type is as expected. // We're more forgiving for extension attributes: if an integer-typed extension attribute // has a value of "10" (i.e. as a string), that's fine. (If it has a value of "garbage", // that will throw in SetAttributeFromString.) ValidateTokenTypeForAttribute(cloudEvent.GetAttribute(name), value.ValueKind); // TODO: This currently performs more conversions than it really should, in the cause of simplicity. // We basically need a matrix of "attribute type vs token type" but that's rather complicated. string attributeValue = value.ValueKind switch { JsonValueKind.String => value.GetString(), JsonValueKind.True => CloudEventAttributeType.Boolean.Format(true), JsonValueKind.False => CloudEventAttributeType.Boolean.Format(false), JsonValueKind.Null => null, // Note: this will fail if the value isn't an integer, or is out of range for Int32. JsonValueKind.Number => CloudEventAttributeType.Integer.Format(value.GetInt32()), _ => throw new ArgumentException($"Invalid token type '{value.ValueKind}' for CloudEvent attribute") }; if (attributeValue is null) { continue; } // Note: we *could* infer an extension type of integer and Boolean, but not other extension types. // (We don't want to assume that everything that looks like a timestamp is a timestamp, etc.) // Stick to strings for consistency. cloudEvent.SetAttributeFromString(name, attributeValue); } }
private void PopulateAttributesFromStructuredEvent(CloudEvent cloudEvent, JObject jObject) { foreach (var keyValuePair in jObject) { var key = keyValuePair.Key; var value = keyValuePair.Value; // Skip the spec version attribute, which we've already taken account of. // Data is handled later, when everything else (importantly, the data content type) // has been populated. if (key == CloudEventsSpecVersion.SpecVersionAttribute.Name || key == DataBase64PropertyName || key == DataPropertyName) { continue; } // For non-extension attributes, validate that the token type is as expected. // We're more forgiving for extension attributes: if an integer-typed extension attribute // has a value of "10" (i.e. as a string), that's fine. (If it has a value of "garbage", // that will throw in SetAttributeFromString.) ValidateTokenTypeForAttribute(cloudEvent.GetAttribute(key), value.Type); // TODO: This currently performs more conversions than it really should, in the cause of simplicity. // We basically need a matrix of "attribute type vs token type" but that's rather complicated. string attributeValue = value.Type switch { JTokenType.String => (string)value, JTokenType.Boolean => CloudEventAttributeType.Boolean.Format((bool)value), JTokenType.Null => null, JTokenType.Integer => CloudEventAttributeType.Integer.Format((int)value), _ => throw new ArgumentException($"Invalid token type '{value.Type}' for CloudEvent attribute") }; if (attributeValue is null) { continue; } // Note: we *could* infer an extension type of integer and Boolean, but not other extension types. // (We don't want to assume that everything that looks like a timestamp is a timestamp, etc.) // Stick to strings for consistency. cloudEvent.SetAttributeFromString(key, attributeValue); } }
// TODO: If we make this private, we'll have significantly more control over what token types we see. // For example, we could turn off date parsing entirely, and we may never get "Uri" tokens either. public CloudEvent DecodeJObject(JObject jObject, IEnumerable <CloudEventAttribute> extensionAttributes = null) { CloudEventsSpecVersion specVersion = CloudEventsSpecVersion.Default; if (jObject.TryGetValue(CloudEventsSpecVersion.SpecVersionAttribute.Name, out var specVersionToken)) { string versionId = (string)specVersionToken; specVersion = CloudEventsSpecVersion.FromVersionId(versionId); // TODO: Throw if specVersion is null? } var cloudEvent = new CloudEvent(specVersion, extensionAttributes); foreach (var keyValuePair in jObject) { var key = keyValuePair.Key; var value = keyValuePair.Value; // Skip the spec version attribute, which we've already taken account of. if (key == CloudEventsSpecVersion.SpecVersionAttribute.Name) { continue; } // TODO: Is the data_base64 name version-specific? if (specVersion == CloudEventsSpecVersion.V1_0 && key == DataBase64) { // Handle base64 encoded binaries cloudEvent.Data = Convert.FromBase64String((string)value); continue; } if (key == Data) { // FIXME: Deserialize where appropriate. // Consider whether there are any options here to consider beyond "string" and "object". // (e.g. arrays, numbers etc). // Note: the cast to "object" is important here, otherwise the string branch is implicitly // converted back to JToken... cloudEvent.Data = value.Type == JTokenType.String ? (string)value : (object)value; continue; } var attribute = cloudEvent.GetAttribute(key); // Set the attribute in the event, taking account of mismatches between the type in the JObject // and the attribute type as best we can. // TODO: This currently performs more conversions than it really should, in the cause of simplicity. // We basically need a matrix of "attribute type vs token type" but that's rather complicated. string attributeValue = value.Type switch { JTokenType.String => (string)value, JTokenType.Date => CloudEventAttributeType.Timestamp.Format((DateTimeOffset)value), JTokenType.Uri => CloudEventAttributeType.UriReference.Format((Uri)value), JTokenType.Null => null, // TODO: Check we want to do this. It's a bit weird. JTokenType.Integer => CloudEventAttributeType.Integer.Format((int)value), _ => throw new ArgumentException($"Invalid token type '{value.Type}' for CloudEvent attribute") }; cloudEvent.SetAttributeFromString(key, attributeValue); } return(cloudEvent); }