Browse Source

Resolve mutability issues with EmbedBuilder. (#1010)

* Create new entities on each build call. Added Length property to EmbedBuilder.

* Resolve Length issues per #1012
tags/2.0
Alex Gravely Christopher F 7 years ago
parent
commit
2988b38ea8
1 changed files with 68 additions and 70 deletions
  1. +68
    -70
      src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs

+ 68
- 70
src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs View File

@@ -1,89 +1,105 @@
using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;


namespace Discord namespace Discord
{ {
public class EmbedBuilder public class EmbedBuilder
{ {
private readonly Embed _embed;
private string _title;
private string _description;
private string _url;
private EmbedImage? _image;
private EmbedThumbnail? _thumbnail;
private List<EmbedFieldBuilder> _fields;


public const int MaxFieldCount = 25; public const int MaxFieldCount = 25;
public const int MaxTitleLength = 256; public const int MaxTitleLength = 256;
public const int MaxDescriptionLength = 2048; public const int MaxDescriptionLength = 2048;
public const int MaxEmbedLength = 6000; // user bot limit is 2000, but we don't validate that here.
public const int MaxEmbedLength = 6000;


public EmbedBuilder() public EmbedBuilder()
{ {
_embed = new Embed(EmbedType.Rich);
Fields = new List<EmbedFieldBuilder>(); Fields = new List<EmbedFieldBuilder>();
} }


public string Title public string Title
{ {
get => _embed.Title;
get => _title;
set set
{ {
if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title)); if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title));
_embed.Title = value;
_title = value;
} }
} }

public string Description public string Description
{ {
get => _embed.Description;
get => _description;
set set
{ {
if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description));
_embed.Description = value;
_description = value;
} }
} }


public string Url public string Url
{ {
get => _embed.Url;
get => _url;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url));
_embed.Url = value;
_url = value;
} }
} }
public string ThumbnailUrl public string ThumbnailUrl
{ {
get => _embed.Thumbnail?.Url;
get => _thumbnail?.Url;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl));
_embed.Thumbnail = new EmbedThumbnail(value, null, null, null);
_thumbnail = new EmbedThumbnail(value, null, null, null);
} }
} }
public string ImageUrl public string ImageUrl
{ {
get => _embed.Image?.Url;
get => _image?.Url;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl));
_embed.Image = new EmbedImage(value, null, null, null);
_image = new EmbedImage(value, null, null, null);
} }
} }
public DateTimeOffset? Timestamp { get => _embed.Timestamp; set { _embed.Timestamp = value; } }
public Color? Color { get => _embed.Color; set { _embed.Color = value; } }

public EmbedAuthorBuilder Author { get; set; }
public EmbedFooterBuilder Footer { get; set; }
private List<EmbedFieldBuilder> _fields;
public List<EmbedFieldBuilder> Fields public List<EmbedFieldBuilder> Fields
{ {
get => _fields; get => _fields;
set set
{ {

if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields));
if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields));
_fields = value; _fields = value;
} }
} }


public DateTimeOffset? Timestamp { get; set; }
public Color? Color { get; set; }
public EmbedAuthorBuilder Author { get; set; }
public EmbedFooterBuilder Footer { get; set; }

public int Length
{
get
{
int titleLength = Title?.Length ?? 0;
int authorLength = Author?.Name?.Length ?? 0;
int descriptionLength = Description?.Length ?? 0;
int footerLength = Footer?.Text?.Length ?? 0;
int fieldSum = Fields.Sum(f => f.Name.Length + f.Value.ToString().Length);

return titleLength + authorLength + descriptionLength + footerLength + fieldSum;
}
}

public EmbedBuilder WithTitle(string title) public EmbedBuilder WithTitle(string title)
{ {
Title = title; Title = title;
@@ -180,7 +196,6 @@ namespace Discord
AddField(field); AddField(field);
return this; return this;
} }

public EmbedBuilder AddField(EmbedFieldBuilder field) public EmbedBuilder AddField(EmbedFieldBuilder field)
{ {
if (Fields.Count >= MaxFieldCount) if (Fields.Count >= MaxFieldCount)
@@ -195,63 +210,54 @@ namespace Discord
{ {
var field = new EmbedFieldBuilder(); var field = new EmbedFieldBuilder();
action(field); action(field);
this.AddField(field);
AddField(field);
return this; return this;
} }


public Embed Build() public Embed Build()
{ {
_embed.Footer = Footer?.Build();
_embed.Author = Author?.Build();
if (Length > MaxEmbedLength)
throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}");

var fields = ImmutableArray.CreateBuilder<EmbedField>(Fields.Count); var fields = ImmutableArray.CreateBuilder<EmbedField>(Fields.Count);
for (int i = 0; i < Fields.Count; i++) for (int i = 0; i < Fields.Count; i++)
fields.Add(Fields[i].Build()); fields.Add(Fields[i].Build());
_embed.Fields = fields.ToImmutable();


if (_embed.Length > MaxEmbedLength)
{
throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}");
}

return _embed;
return new Embed(EmbedType.Rich, Title, Description, Url, Timestamp, Color, _image, null, Author?.Build(), Footer?.Build(), null, _thumbnail, fields.ToImmutable());
} }
} }


public class EmbedFieldBuilder public class EmbedFieldBuilder
{ {
private string _name;
private string _value;
private EmbedField _field; private EmbedField _field;

public const int MaxFieldNameLength = 256; public const int MaxFieldNameLength = 256;
public const int MaxFieldValueLength = 1024; public const int MaxFieldValueLength = 1024;


public string Name public string Name
{ {
get => _field.Name;
get => _name;
set set
{ {
if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name));
if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name));
_field.Name = value;
_name = value;
} }
} }


public object Value public object Value
{ {
get => _field.Value;
get => _value;
set set
{ {
var stringValue = value?.ToString(); var stringValue = value?.ToString();
if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value));
if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value));
_field.Value = stringValue;
_value = stringValue;
} }
} }
public bool IsInline { get => _field.Inline; set { _field.Inline = value; } }

public EmbedFieldBuilder()
{
_field = new EmbedField();
}
public bool IsInline { get; set; }


public EmbedFieldBuilder WithName(string name) public EmbedFieldBuilder WithName(string name)
{ {
@@ -270,48 +276,44 @@ namespace Discord
} }


public EmbedField Build() public EmbedField Build()
=> _field;
=> new EmbedField(Name, Value.ToString(), IsInline);
} }


public class EmbedAuthorBuilder public class EmbedAuthorBuilder
{ {
private EmbedAuthor _author;

private string _name;
private string _url;
private string _iconUrl;
public const int MaxAuthorNameLength = 256; public const int MaxAuthorNameLength = 256;


public string Name public string Name
{ {
get => _author.Name;
get => _name;
set set
{ {
if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name));
_author.Name = value;
_name = value;
} }
} }
public string Url public string Url
{ {
get => _author.Url;
get => _url;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(Url));
_author.Url = value;
_url = value;
} }
} }
public string IconUrl public string IconUrl
{ {
get => _author.IconUrl;
get => _iconUrl;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl));
_author.IconUrl = value;
_iconUrl = value;
} }
} }


public EmbedAuthorBuilder()
{
_author = new EmbedAuthor();
}

public EmbedAuthorBuilder WithName(string name) public EmbedAuthorBuilder WithName(string name)
{ {
Name = name; Name = name;
@@ -329,39 +331,35 @@ namespace Discord
} }


public EmbedAuthor Build() public EmbedAuthor Build()
=> _author;
=> new EmbedAuthor(Name, Url, IconUrl, null);
} }


public class EmbedFooterBuilder public class EmbedFooterBuilder
{ {
private EmbedFooter _footer;
private string _text;
private string _iconUrl;


public const int MaxFooterTextLength = 2048; public const int MaxFooterTextLength = 2048;


public string Text public string Text
{ {
get => _footer.Text;
get => _text;
set set
{ {
if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text));
_footer.Text = value;
_text = value;
} }
} }
public string IconUrl public string IconUrl
{ {
get => _footer.IconUrl;
get => _iconUrl;
set set
{ {
if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl)); if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(IconUrl));
_footer.IconUrl = value;
_iconUrl = value;
} }
} }


public EmbedFooterBuilder()
{
_footer = new EmbedFooter();
}

public EmbedFooterBuilder WithText(string text) public EmbedFooterBuilder WithText(string text)
{ {
Text = text; Text = text;
@@ -374,6 +372,6 @@ namespace Discord
} }


public EmbedFooter Build() public EmbedFooter Build()
=> _footer;
=> new EmbedFooter(Text, IconUrl, null);
} }
} }

Loading…
Cancel
Save