Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Media Post Implementation #72

Closed
wants to merge 25 commits into from
Closed

Conversation

leobragaz
Copy link
Contributor

@leobragaz leobragaz commented Jan 8, 2020

Description

This PR implements MediaPost and relative methods.

TODO:

  • Implement types;
  • Implement message;
  • Update codecs;
  • Implement keeper methods;
  • Implement handler methods;
  • Implement querier methods;
  • Wrote CLI commands.

Checklist

  • Targeted PR against correct branch.
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Wrote tests.
  • Added an entry to the CHANGELOG.md file.
  • Re-reviewed Files changed in the Github PR explorer.

@leobragaz leobragaz self-assigned this Jan 8, 2020
Bragaz added 2 commits January 9, 2020 11:26
updated codecs for post and media post
implemented keeper methods for media post
implemented strings type and some useful methods
@codecov-io
Copy link

codecov-io commented Jan 9, 2020

Codecov Report

❗ No coverage uploaded for pull request base (master@6f45ccb). Click here to learn what that means.
The diff coverage is 63.55%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master      #72   +/-   ##
=========================================
  Coverage          ?   75.99%           
=========================================
  Files             ?       21           
  Lines             ?     1083           
  Branches          ?        0           
=========================================
  Hits              ?      823           
  Misses            ?      238           
  Partials          ?       22
Impacted Files Coverage Δ
x/posts/internal/types/codec.go 100% <ø> (ø)
x/posts/internal/types/post_optional_data.go 0% <0%> (ø)
x/posts/internal/types/post.go 0% <0%> (ø)
x/posts/internal/types/querier.go 0% <0%> (ø)
x/posts/internal/keeper/handler.go 95.61% <100%> (ø)
x/posts/internal/keeper/keeper.go 98.23% <100%> (ø)
x/posts/internal/types/media_post.go 81.41% <100%> (ø)
x/posts/internal/types/post_response.go 53.84% <100%> (ø)
x/posts/internal/types/reaction.go 82.85% <100%> (ø)
x/posts/internal/types/text_post.go 89.04% <78.12%> (ø)
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6f45ccb...54c4795. Read the comment docs.

@RiccardoM
Copy link
Contributor

@bragaz I've noticed that currently you've implemented the Provider support and MimeType checks.

I personally think we should follow what @kwunyeung suggested inside his comment on #72:

We cannot and not necessary to verify the mime types on-chain. It depends on how the applications work and specify it as the uploading and rendering parts won't be done on-chain.

And

[...] Storing the mime types are for the applications to know how the files should be rendered. So I don't think the Provider is necessary in any cases.

This means that there is no need for on-chain verification of mime types verification nor provider support at all.

Do you think they should be kept? If so, why?

@leobragaz
Copy link
Contributor Author

leobragaz commented Jan 10, 2020

Yes I was referring to your comment in #36 :

Such lists can be used as reference to know what providers and MIME types are supported.
They can also be used by the governance to add a new provider or a new MIME type to the supported list

I Miss @kwunyeung 's one and he's totally right, the apps should take care of that. This will also keep chain cleaner and clients could implement as many providers they want.

Bragaz added 4 commits January 10, 2020 17:00
edited all method in keeper, handler and querier to fit the new models
todo: Fix tests
- edited keeper_test, handler_test, querier_test to fit the new Post Interface
- edited Post response to fit the new Post Interface
- updated codec
- added new tests for media posts

TODO:
- complete MsgCreateMediaPost tests
- edit CLI command to let user make media posts
todo:
- fix bugs in marshalJSON, unmarshalJSON
package types_test

import (
"fmt"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File is not goimports-ed (from goimports)

Suggested change
"fmt"
"fmt"
"testing"

@leobragaz
Copy link
Contributor Author

leobragaz commented Jan 14, 2020

I've been struggling all day with MarshalJSON() UnmarshalJSON() methods of MediaPost and I wasn't able to find any good implementation, @RiccardoM do you have any suggestions on how to do these method starting by String()?

@RiccardoM
Copy link
Contributor

RiccardoM commented Jan 14, 2020

I've been struggling all day with MarshalJSON() UnmarshalJSON() methods of MediaPost and I wasn't able to find any good implementation, @RiccardoM do you have any suggestions on how to do these method starting by String()?

You are doing the exact opposite of what should be done. If you take a look at the TextPost, I have:

  1. Overridden the MashalJson with a custom implementation
  2. Overridden the String method with a custom implementation that calls the MarshalJson method

You are overriding the MarshalJson calling the String method, which will not work at all as the String method returns just the class name by default.

So the good implementation should be something like

func (mp MediaPost) MarshalJson() ([]byte, err) {
  type mediaPostJson MediaPost
  return json.Marshal(mediaPostJson(mp))
}

func (mp MediaPost) String() string {
  json, err := json.Marshal(&mp) 
  if err != nil {  
    panic(err)
  }
  return string(json)
}

Actually, it's not needed to use the json.Marshal from the String method and is actually not the best option to be honest.

We should change it inside TextPost too, so that the String method returns less data in a plain format, much like it does for the Posts type. It should be better to open a new issue for this.

Edit

For future reference, you can look at existing code. For example, you could have found the implementation for:

@leobragaz
Copy link
Contributor Author

I was doing that way since I discovered through the String() test that somehow, it was serializing the media post without the Medias, so just the first object of MediaPost. After that, I thought that it was because it was an array and needed to be handled differently...and so i tried to implement everything like you saw.

@RiccardoM
Copy link
Contributor

I was doing that way since I discovered through the String() test that somehow, it was serializing the media post without the Medias, so just the first object of MediaPost. After that, I thought that it was because it was an array and needed to be handled differently...and so i tried to implement everything like you saw.

I would prefer we stick to an implementation like the one I've suggested, and we later discuss any problem that might raise individually once we have the tests setup and failing.

Please don't change the implementations completely just because tests are failing, and do not remove the tests too. Keep them as it might be useful to understand what's wrong.

@leobragaz leobragaz marked this pull request as ready for review January 15, 2020 13:26
@leobragaz
Copy link
Contributor Author

Besides the conflicts, we need to understand why the marshalling of MediaPost don't include/overwrite the medias part. All failing tests are about it.
MediaPost TextPost value is marshalled correctly, but it's the only one.
E.g.
With the String() method implemented as follow:

// String implements fmt.Stringer
func (pm PostMedia) String() string {
	bytes, err := json.Marshal(&pm)
	if err != nil {
		panic(err)
	}

	return string(bytes)
}

The result of the function's test is this:

{
  "id": "2",
  "parent_id": "0",
  "message": "media Post",
  "created": "0",
  "last_edited": "0",
  "allows_comments": false,
  "subspace": "desmos",
  "creator": "cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns"
}

And it's wrong because I think we need to get something like:

{
  "post": {
    "id": "2",
    "parent_id": "0",
    "message": "media Post",
    "created": "0",
    "last_edited": "0",
    "allows_comments": false,
    "subspace": "desmos",
    "creator": "cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns"
  },
  "medias": [
    {
      "provider": "ipfs",
      "uri": "uri",
      "mime_Type": "text/plain"
    }
  ]
}

I've tried to implement String() as follow:

func (mp MediaPost) String() string {
	bytes, err := mp.TextPost.MarshalJSON()
	bytes2, err := json.Marshal(&mp.Medias)
	bb := append(bytes[:], bytes2[:]...)
	if err != nil {
		panic(err)
	}
	return string(bb)
}

It isn't printing a beautiful result but at least print medias...I really don't understand why this is happening..maybe I'm missing something! Let me know.

This problem is the same in PostQueryResponse.

@RiccardoM
Copy link
Contributor

@bragaz Please pull from master to solve the existing conflicts

Bragaz added 3 commits January 15, 2020 15:01
…eonardo/media-posts-impl

� Conflicts:
�	x/posts/client/cli/tx.go
�	x/posts/client/rest/tx.go
�	x/posts/internal/keeper/common_test.go
�	x/posts/internal/keeper/handler.go
�	x/posts/internal/keeper/handler_test.go
�	x/posts/internal/keeper/keeper.go
�	x/posts/internal/keeper/keeper_test.go
�	x/posts/internal/keeper/querier_test.go
�	x/posts/internal/types/msgs.go
�	x/posts/internal/types/msgs_test.go
�	x/posts/internal/types/post.go
�	x/posts/internal/types/post_response_test.go
�	x/posts/internal/types/text_post_test.go
�	x/posts/legacy/v0.2.0/migrate.go
�	x/posts/legacy/v0.2.0/types.go
@RiccardoM
Copy link
Contributor

@bragaz I had the time to look deeper into this, and I have found something really interesting.

How JSON encoding works and how to fix the problem

Currently we have the following type inheritances:

  Post 
   ^
TextPost
   ^
MediaPost

Both TextPost and MediaPost implement MarshalJSON. What happens is that during the call of json.Marshal(m), Go performs a lookup of the MarshalJson method in a top-down fashion starting from Post.

What does this mean? It means that it stops as soon as one type implements MarshalJson and uses that implementation. In our cases, it stopped at TextPost, which resulted in every MediaPost being serialized as a TextPost object instead.

By removing the implementation of both MarshalJSON and UnmarshalJSON from TextPost, it starts working properly again. The results are:

var m MediaPost = ...
json.Marshal(m)

// {"id":"2","parent_id":"0","message":"media Post","created":"2020-01-01T15:15:00Z","last_edited":"0001-01-01T00:00:00Z","allows_comments":false,"subspace":"desmos","creator":"cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns","medias":[{"uri":"uri","provider":"provider","mime_Type":"text/plain"}]}

It is completely fine to remove both MarshalJSON and UnmarshalJSON from TextPost as it has no custom implementation that Amino does not handle properly.

However, we need to implement those methods into MediaPost as it encapsulates TextPost and Amino does not handle very well encapsulation.

Changes in the serialization fashion

Looking at this, it made me think about the best way we can handle all of this properly. What we could do is the following.

JSON serialized posts that extend TextPost should add to it the new fields. This means overriding the MarshalJSON and UnmarshalJSON for each extending post type.

This will result in MediaPosts being serialized as follows:

{
  "id": "2",
  "parent_id": "0",
  "message": "media Post",
  "created": "2020-01-01T15:15:00Z",
  "last_edited": "0001-01-01T00:00:00Z",
  "allows_comments": false,
  "subspace": "desmos",
  "creator": "cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns",
  "medias": [
    {
      "uri": "uri",
      "provider": "provider",
      "mime_Type": "text/plain"
    }
  ]
}

As you can see, we do not encapsulate the TextPost into a "post" object, but we still have the new "medias" array.

This results in a more clean serialization overall.

Problems with this approach

Thinking ahead of time, this solution might however bring some new problems when we will start to implement poll posts (#14) too.

What would happen if a users wants to create a poll having an image inside?

This should result in a hybrid type that inherits from both MediaPost and PollPost, but as they will have both overridden the MarshalJSON method, one of the two will be chosen instead of the new one.

However, we can think this thought later, but it is still important to now it now.

Copy link
Contributor

@RiccardoM RiccardoM left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed just the implementations and not the tests. Will review the latter once the issues and comments on the first have been completely solved.

CHANGELOG.md Outdated Show resolved Hide resolved
x/posts/client/cli/tx.go Show resolved Hide resolved
x/posts/client/cli/tx.go Show resolved Hide resolved
x/posts/client/cli/tx.go Outdated Show resolved Hide resolved
x/posts/client/cli/tx.go Outdated Show resolved Hide resolved
x/posts/legacy/v0.1.0/types.go Outdated Show resolved Hide resolved
x/posts/legacy/v0.1.0/types.go Outdated Show resolved Hide resolved
x/posts/alias.go Outdated Show resolved Hide resolved
x/posts/alias.go Outdated Show resolved Hide resolved
x/posts/internal/types/codec.go Show resolved Hide resolved
@RiccardoM RiccardoM added the kind/new-feature Propose the addition of a new feature that does not yet exist label Jan 17, 2020
@RiccardoM RiccardoM added this to the v0.3.0 milestone Jan 17, 2020
x/posts/client/cli/tx.go Outdated Show resolved Hide resolved
x/posts/client/cli/tx.go Show resolved Hide resolved
x/posts/client/rest/tx.go Outdated Show resolved Hide resolved
x/posts/client/rest/tx.go Outdated Show resolved Hide resolved
x/posts/internal/types/media_post.go Show resolved Hide resolved
x/posts/internal/types/codec.go Show resolved Hide resolved
@leobragaz
Copy link
Contributor Author

Removing

cdc.RegisterInterface((*Post)(nil), nil)

Will result in some errors in tests and handler because it's an unregistered interface.

- waiting for removing provider
arg := strings.Split(args[i], ",")
if len(arg) == 3 {
media := types.NewPostMedia(arg[0], arg[1], arg[2])
medias = append(medias, media)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would happen if the user specifies two times the same media data? Should we use something like appendIfMissing instead of append here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that when I was implementing it...it is good to forbid user to post the same media more times? I mean, I can post the same GIF two times, but that will not be the same for a video or a PDF...so yeah, we should use appendIfMissing instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bragaz Using appendIfMissing here means that we are not allowing to use the same media inside the same post. E.g., the user will not be able to create a post having the same exact GIF, PDF, or file in it.

This is correct, and does not refer to creating two posts with the same media inside. This means

  • Alice creating post P1 with media M1 will be allowed
  • Alice creating post P1 with media M1 and M2 will be allowed
  • Alice creating post P1 with media M1 and M1 (again) will not be allowed
  • Alice creating post P1 with media M1 and post P2 with the same media M1 will be allowed

return sdk.ErrUnknownRequest("If medias are present, you should specify uri, provider and mime type, if you are confused, please use the --help flag")
}
}
if textMsg, ok := msg.(types.MsgCreateTextPost); ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check will always succeed, can we simply delete it and replace it with something like the following?

msg = types.NewMsgCreateMediaPost(MsgCreateTextPost(msg), medias))

addr, req.CreationTime)

if len(req.Medias) != 0 {
if textMsg, ok := msg.(types.MsgCreateTextPost); ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check will always succeed. Can we replace it with something like the following?

msg = types.NewMsgCreateMediaPost(MsgCreateTextPost(msg), req.Medias)

leobragaz and others added 3 commits January 20, 2020 09:32
Co-Authored-By: Riccardo Montagnin <riccardo.montagnin@gmail.com>
Co-Authored-By: Riccardo Montagnin <riccardo.montagnin@gmail.com>
@leobragaz
Copy link
Contributor Author

Following the discussion made about Interfaces and types, I'm closing this PR in order to open another one with the following changes:

  • Remove all the interfaces
  • A single Post implementation as follow:
type Post struct {
  // All TextPost's field
  Medias []Media
}
type MsgCreatePost {
  // All TextPost's field
  Medias []Media
}

@leobragaz leobragaz closed this Jan 20, 2020
@leobragaz leobragaz mentioned this pull request Jan 21, 2020
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/new-feature Propose the addition of a new feature that does not yet exist
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants