Merge pull request #101 from SuperTux88/add-embed-entity

Add embed entity
This commit is contained in:
Benjamin Neff 2018-10-05 21:45:45 +02:00
commit 8d6006ce73
No known key found for this signature in database
GPG key ID: 971464C3F1A90194
14 changed files with 298 additions and 4 deletions

69
docs/_entities/embed.md Normal file
View file

@ -0,0 +1,69 @@
---
title: Embed
---
This entity represents the embed information about an URL that should be
embedded, it is nested in a [StatusMessage][status_message]. To embed a URL
means to keep an embedded representation or a preview of a third party
resource referenced by the URL inside the status message.
* If this entity is present, the receiving server should only embed the included
`url` and not search for other URLs to embed.
* If the included `url` is a
trusted oEmbed provider, the server should query the oEmbed data.
* If `title`, `description` or `image` are missing, the server should query the
information from the URL (oEmbed or OpenGraph).
* If `nothing` is `true` the server should not embed any URLs.
A link to the embedded resource should also be included in the `text` of the
[StatusMessage][status_message] for accessibility reasons, otherwise it could
happen that some people don't see the link, for example when this entity isn't
implemented or where no embeds are supported at all. However, it is possible
that the link in the `text` and the `url` here are different, because some sites
have different URLs in `og:url` as requested.
## Optional Properties
All properties are optional, but either `url` is required or `nothing` must be `true`.
| Property | Type (Length) | Description |
| ------------- | ------------------------ | ------------------------------------- |
| `url` | [URL][url] (65535) | The URL that should be embedded. |
| `title` | [String][string] (255) | The title of the embedded URL. |
| `description` | [String][string] (65535) | The description of the embedded URL. |
| `image` | [URL][url] (65535) | The image of the embedded URL. |
| `nothing` | [Boolean][boolean] | `true` if nothing should be embedded. |
## Example
### Only `url`
~~~xml
<embed>
<url>https://example.org/</url>
</embed>
~~~
### With metadata
~~~xml
<embed>
<url>https://example.org/</url>
<title>Example Website</title>
<description>This is an example!</description>
<image>https://example.org/example.png</image>
</embed>
~~~
### With `nothing`
~~~xml
<embed>
<nothing>true</nothing>
</embed>
~~~
[string]: {{ site.baseurl }}/federation/types.html#string
[url]: {{ site.baseurl }}/federation/types.html#url
[boolean]: {{ site.baseurl }}/federation/types.html#url
[status_message]: {{ site.baseurl }}/entities/status_message.html

View file

@ -24,6 +24,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po
| `photo` | [Photo][photo]s | ✔ | The attached Photos of the status message, the `status_message_guid` and the `author` need to match the status message. |
| `poll` | [Poll][poll] | ✘ | The attached Poll of the status message. |
| `event` | [Event][event] | ✘ | The attached Event of the status message. |
| `embed` | [Embed][embed] | ✔ | The Embed information of an URL that should be embedded in the status message. |
## Examples
@ -47,12 +48,12 @@ This entity represents a reshare of a status message. It inherits from [Post][po
<guid>c3893bf029e7013487753131731751e9</guid>
<created_at>2016-07-11T22:50:18Z</created_at>
<text>I am a very interesting status update</text>
<public>true</public>
<location>
<address>Vienna, Austria</address>
<lat>48.208174</lat>
<lng>16.373819</lng>
</location>
<public>true</public>
</status_message>
~~~
@ -64,6 +65,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po
<guid>e05828d029e7013487753131731751e9</guid>
<created_at>2016-07-11T22:52:56Z</created_at>
<text>I am a very interesting status update</text>
<public>true</public>
<photo>
<guid>0788070029e8013487753131731751e9</guid>
<author>alice@example.org</author>
@ -86,7 +88,6 @@ This entity represents a reshare of a status message. It inherits from [Post][po
<height>480</height>
<width>800</width>
</photo>
<public>true</public>
</status_message>
~~~
@ -99,6 +100,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po
<guid>378473f029e9013487753131731751e9</guid>
<created_at>2016-07-11T23:00:42Z</created_at>
<text>I am a very interesting status update</text>
<public>true</public>
<poll>
<guid>2a22d6c029e9013487753131731751e9</guid>
<question>Select an answer</question>
@ -115,7 +117,24 @@ This entity represents a reshare of a status message. It inherits from [Post][po
<answer>Maybe</answer>
</poll_answer>
</poll>
</status_message>
~~~
### With [Embed][embed]
~~~xml
<status_message>
<author>alice@example.org</author>
<guid>378473f029e9013487753131731751e9</guid>
<created_at>2016-07-11T23:00:42Z</created_at>
<text>I am a very interesting status update</text>
<public>true</public>
<embed>
<url>https://example.org/</url>
<title>Example Website</title>
<description>This is an example!</description>
<image>https://example.org/example.png</image>
</embed>
</status_message>
~~~
@ -128,6 +147,7 @@ This entity represents a reshare of a status message. It inherits from [Post][po
<created_at>2016-07-11T23:02:24Z</created_at>
<provider_display_name>mobile</provider_display_name>
<text>i am a very interesting status update</text>
<public>true</public>
<photo>
<guid>0788070029e8013487753131731751e9</guid>
<author>alice@example.org</author>
@ -171,7 +191,12 @@ This entity represents a reshare of a status message. It inherits from [Post][po
<answer>Maybe</answer>
</poll_answer>
</poll>
<public>true</public>
<embed>
<url>https://example.org/</url>
<title>Example Website</title>
<description>This is an example!</description>
<image>https://example.org/example.png</image>
</embed>
</status_message>
~~~
@ -186,3 +211,4 @@ This entity represents a reshare of a status message. It inherits from [Post][po
[photo]: {{ site.baseurl }}/entities/photo.html
[poll]: {{ site.baseurl }}/entities/poll.html
[event]: {{ site.baseurl }}/entities/event.html
[embed]: {{ site.baseurl }}/entities/embed.html

View file

@ -30,6 +30,7 @@ require "diaspora_federation/entities/poll_answer"
require "diaspora_federation/entities/poll"
require "diaspora_federation/entities/poll_participation"
require "diaspora_federation/entities/embed"
require "diaspora_federation/entities/location"
require "diaspora_federation/entities/event"

View file

@ -0,0 +1,44 @@
module DiasporaFederation
module Entities
# This entity is used to specify embed information about an URL that should be embedded.
#
# @see Validators::EmbedValidator
class Embed < Entity
# @!attribute [r] url
# URL that should be embedded.
# @return [String] url
property :url, :string, optional: true
# @!attribute [r] title
# The title of the embedded URL.
# @return [String] title
property :title, :string, optional: true
# @!attribute [r] description
# The description of the embedded URL.
# @return [String] description
property :description, :string, optional: true
# @!attribute [r] image
# The image of the embedded URL.
# @return [String] image
property :image, :string, optional: true
# @!attribute [r] nothing
# True, if nothing should be embedded.
# @return [String] nothing
property :nothing, :boolean, optional: true
# @return [String] string representation of this object
def to_s
"Embed#{":#{url}" if url}"
end
def validate
super
raise ValidationError, "Either 'url' must be set or 'nothing' must be 'true'" unless nothing ^ url
end
end
end
end

View file

@ -36,6 +36,11 @@ module DiasporaFederation
# @return [Entities::Event] event
entity :event, Entities::Event, optional: true
# @!attribute [r] embed
# Optional embed information of an URL that should be embedded in the status message
# @return [Entities::Embed] embed
entity :embed, Entities::Embed, optional: true
private
def validate

View file

@ -10,6 +10,7 @@
{"$ref": "#/definitions/reshare"},
{"$ref": "#/definitions/profile"},
{"$ref": "#/definitions/location"},
{"$ref": "#/definitions/embed"},
{"$ref": "#/definitions/photo"},
{"$ref": "#/definitions/poll"},
{"$ref": "#/definitions/poll_answer"}
@ -373,6 +374,26 @@
]
}
}
},
"embed": {
"type": "object",
"properties": {
"entity_type": {
"type": "string",
"pattern": "^embed$"
},
"entity_data": {
"type": "object",
"properties": {
"url": { "type": ["string", "null"] },
"title": { "type": ["string", "null"] },
"description": { "type": ["string", "null"] },
"image": { "type": ["string", "null"] },
"nothing": { "type": "boolean" }
}
}
}
}
}
}

View file

@ -207,6 +207,14 @@ module DiasporaFederation
edited_at { Time.now.utc }
end
Fabricator(:embed_entity, class_name: DiasporaFederation::Entities::Embed) do
url "https://example.org/"
title "Example Website"
description "This is an example!"
image "https://example.org/example.png"
nothing nil
end
Fabricator(:related_entity, class_name: DiasporaFederation::Entities::RelatedEntity) do
author { Fabricate.sequence(:diaspora_id) }
local true

View file

@ -46,6 +46,7 @@ require "diaspora_federation/validators/account_migration_validator"
require "diaspora_federation/validators/comment_validator"
require "diaspora_federation/validators/contact_validator"
require "diaspora_federation/validators/conversation_validator"
require "diaspora_federation/validators/embed_validator"
require "diaspora_federation/validators/event_participation_validator"
require "diaspora_federation/validators/event_validator"
require "diaspora_federation/validators/h_card_validator"

View file

@ -0,0 +1,13 @@
module DiasporaFederation
module Validators
# This validates a {Entities::Embed}.
class EmbedValidator < OptionalAwareValidator
include Validation
rule :url, :URI
rule :title, length: {maximum: 255}
rule :description, length: {maximum: 65_535}
rule :image, URI: %i[host path]
end
end
end

View file

@ -0,0 +1,56 @@
module DiasporaFederation
describe Entities::Embed do
let(:data) { Fabricate.attributes_for(:embed_entity) }
let(:xml) { <<-XML }
<embed>
<url>#{data[:url]}</url>
<title>#{data[:title]}</title>
<description>#{data[:description]}</description>
<image>#{data[:image]}</image>
</embed>
XML
let(:json) { <<-JSON }
{
"entity_type": "embed",
"entity_data": {
"url": "#{data[:url]}",
"title": "#{data[:title]}",
"description": "#{data[:description]}",
"image": "#{data[:image]}"
}
}
JSON
let(:string) { "Embed:#{data[:url]}" }
it_behaves_like "an Entity subclass"
it_behaves_like "an XML Entity"
it_behaves_like "a JSON Entity"
describe "#validate" do
it "allows 'url' to be set if 'nothing' is not true" do
expect { Entities::Embed.new(data) }.not_to raise_error
end
it "allows 'url' to be missing if 'nothing' is true" do
expect { Entities::Embed.new(nothing: true) }.not_to raise_error
end
it "doesn't allow 'url' to be set if 'nothing' is true" do
expect {
Entities::Embed.new(data.merge(nothing: true))
}.to raise_error Entity::ValidationError, "Either 'url' must be set or 'nothing' must be 'true'"
end
it "doesn't allow 'url' to be missing if 'nothing' is not true" do
expect {
Entities::Embed.new({})
}.to raise_error Entity::ValidationError, "Either 'url' must be set or 'nothing' must be 'true'"
end
end
end
end

View file

@ -10,6 +10,7 @@ module DiasporaFederation
location: location,
poll: nil,
event: nil,
embed: nil,
provider_display_name: "something"
)
}
@ -137,6 +138,8 @@ XML
expect(parsed_instance.photos).to eq([])
expect(parsed_instance.location).to be_nil
expect(parsed_instance.poll).to be_nil
expect(parsed_instance.event).to be_nil
expect(parsed_instance.embed).to be_nil
expect(parsed_instance.public).to be_falsey
expect(parsed_instance.provider_display_name).to be_nil
end

View file

@ -0,0 +1,40 @@
module DiasporaFederation
describe Validators::EmbedValidator do
let(:entity) { :embed_entity }
it_behaves_like "a common validator"
describe "#url" do
it_behaves_like "a property with a value validation/restriction" do
let(:property) { :url }
let(:wrong_values) { %w[https://asdf$%.com example.com] }
let(:correct_values) { [nil, "https://example.org", "https://example.org/index.html"] }
end
end
describe "#title" do
it_behaves_like "a length validator" do
let(:property) { :title }
let(:length) { 255 }
end
end
describe "#description" do
it_behaves_like "a length validator" do
let(:property) { :description }
let(:length) { 65_535 }
end
end
describe "#image" do
it_behaves_like "a property with a value validation/restriction" do
let(:property) { :image }
let(:wrong_values) { %w[https://asdf$%.com example.com] }
let(:correct_values) { [nil] }
end
it_behaves_like "a url path validator" do
let(:property) { :image }
end
end
end
end

View file

@ -27,7 +27,7 @@ module DiasporaFederation
describe "##{prop}" do
it_behaves_like "a property with a value validation/restriction" do
let(:property) { prop }
let(:wrong_values) { %w[https://asdf$.com example.com] }
let(:wrong_values) { %w[https://asdf$%.com example.com] }
let(:correct_values) { [nil] }
end

View file

@ -277,6 +277,13 @@ shared_examples "a url validator without path" do
end
shared_examples "a url path validator" do
it "validates url with a path" do
validator = described_class.new(entity_stub(entity, property => "https://example.com/some/path"))
expect(validator).to be_valid
expect(validator.errors).to be_empty
end
it "fails for url with special chars" do
validator = described_class.new(entity_stub(entity, property => "https://asdf$%.com/some/path"))