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

api: Add getMessageCompat helper, for servers with and without FL 120 #177

Merged
merged 2 commits into from
Jun 13, 2023

Conversation

chrisbobbe
Copy link
Collaborator

@chrisbobbe chrisbobbe commented Jun 12, 2023

We can use this to get raw Markdown content for quote-and-reply
(#116) and for the "Share" option on a message. For those, we only
care about the raw Markdown content and so could just as well have
used the raw_content field on the get-single-message response, for
servers pre-120. But...

We can also use this for #73, "Handle Zulip-internal links by
navigation", to follow /near/ links through topic/stream moves
(see implementation in zulip-mobile). For that, we'll need more than
just the message's raw Markdown.

Fixes: #140

@chrisbobbe chrisbobbe added the a-api Implementing specific parts of the Zulip server API label Jun 12, 2023
@chrisbobbe chrisbobbe requested a review from gnprice June 12, 2023 21:01
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks! Comments below.

final supportsApplyMarkdown = connection.zulipFeatureLevel! >= 120; // TODO(server-5)
assert(supportsApplyMarkdown || applyMarkdown == null);
return connection.get('getMessage', GetMessageResult.fromJson, 'messages/$messageId', {
'message_id': messageId,
Copy link
Member

Choose a reason for hiding this comment

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

This isn't needed — the API calls for it only in the path.

(The way the API docs are arranged isn't super clear about this, but it's there in the orange mention of "IN PATH":
image
and then it's made unambiguous by the example in the curl tab.)


@JsonSerializable(fieldRename: FieldRename.snake)
class GetMessageResult {
final String? rawContent; // (Marked optional for a future where this is removed)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
final String? rawContent; // (Marked optional for a future where this is removed)
// final String rawContent; // deprecated; ignore

It's always safe to leave properties out of the result types, because we never have the deserialization look for unexpected properties to complain about. (Because that would risk incompatibility with future server versions.)

Generally when implementing a route binding we include properties even when we don't anticipate using them, for the reasons at https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#lazy-programming . But when the property is actually deprecated, that changes things: the ideal client in a world of ideal software would then leave the property out, so we leave it out unless there's some practical reason we currently need it.

Comment on lines 34 to 38
final messages = response.messages;
if (messages.isEmpty) {
return null;
}
return messages.first;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
final messages = response.messages;
if (messages.isEmpty) {
return null;
}
return messages.first;
return response.messages.firstOrNull;

Comment on lines 45 to 46
// message property present at this feature level
return response.message!;
Copy link
Member

Choose a reason for hiding this comment

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

This is probably fine in practice, but it's not the ideal behavior. I'd like to try to stick to the ideal behavior, to see if we have a pattern that makes it comfortable to do so.

The reason it's not ideal is that the ! makes it effectively an internal error in our code if the property is missing. Whereas if it is missing, that signals instead that we have a malformed JSON response from the server — one that doesn't match our expectations from the API. Ordinarily, that would cause a MalformedServerResponseException, per this bit of ApiConnection.send:

    try {
      return fromJson(json);
    } catch (e) {
      throw MalformedServerResponseException(
        routeName: routeName, httpStatus: httpStatus, data: json);
    }

So this could instead check for null and throw such an exception in that case.

In this instance, though, I think there's actually a simpler way; see next comment.

@JsonSerializable(fieldRename: FieldRename.snake)
class GetMessageResult {
final String? rawContent; // (Marked optional for a future where this is removed)
final Message? message; // TODO(server-5)
Copy link
Member

Choose a reason for hiding this comment

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

Since rawContent is deprecated and we don't intend to use it, this endpoint doesn't have a remaining use case for older servers where this message property is absent.

Given that, I think we can simplify things a bit by effectively pretending that the pre-120 version of this endpoint doesn't exist. That means:

  • Have Message message, unconditionally.
  • Have getMessage assert that the feature level is at least 120.

(The latter step isn't strictly necessary — without it we'd get an error anyway — but could help clarify things a bit.)

Then that also means getMessageCompat could just return result.message without !.

});

test('legacy; message not found', () {
return FakeApiConnection.with_((connection) async {
Copy link
Member

Choose a reason for hiding this comment

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

nit:

Suggested change
return FakeApiConnection.with_((connection) async {
return FakeApiConnection.with_(zulipFeatureLevel: 119, (connection) async {

(instead of at the end of the callback).

Dart didn't support that syntax until something like a year or two ago — previously named arguments had to come after positional arguments — which is a reason you'll often still see code not take advantage of it. But I think a case like this where the named argument is short and important for understanding the code, and the positional one is many lines long, is an excellent use of it.

Comment on lines 27 to 28
expectLegacy
? (check(connection.lastRequest).isA<http.Request>()
Copy link
Member

Choose a reason for hiding this comment

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

Seems a bit cleaner to make this a normal if/else statement, since we're not using the expression nature of the ?: operator.

final message = eg.streamMessage();
final fakeResult = GetMessageResult(message: message);
connection.prepare(json: fakeResult.toJson());
final result = await checkGetMessageCompat(connection, false,
Copy link
Member

Choose a reason for hiding this comment

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

Let's make expectLegacy a named parameter; I think that will make these call sites easier to read, without having to refer back to the definition to remember what the boolean means and which direction it runs in.

@chrisbobbe chrisbobbe force-pushed the pr-get-message-compat branch from 2305e03 to 3a96b23 Compare June 13, 2023 19:03
@chrisbobbe
Copy link
Collaborator Author

Thanks for the review! Revision pushed.

@chrisbobbe chrisbobbe force-pushed the pr-get-message-compat branch 2 times, most recently from e9c3582 to 881c233 Compare June 13, 2023 19:14
We can use this to get raw Markdown content for quote-and-reply
(zulip#116) and for the "Share" option on a message. For those, we only
care about the raw Markdown content and so could just as well have
used the `raw_content` field on the get-single-message response, for
servers pre-120. But...

We can also use this for zulip#73, "Handle Zulip-internal links by
navigation", to follow /near/<id> links through topic/stream moves
(see implementation in zulip-mobile). For that, we'll need more than
just the message's raw Markdown.
@gnprice gnprice force-pushed the pr-get-message-compat branch from 881c233 to 631f4d6 Compare June 13, 2023 19:36
@gnprice
Copy link
Member

gnprice commented Jun 13, 2023

Thanks! Looks good; merging.

@gnprice gnprice merged commit 631f4d6 into zulip:main Jun 13, 2023
@chrisbobbe chrisbobbe deleted the pr-get-message-compat branch June 13, 2023 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-api Implementing specific parts of the Zulip server API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

api: Add binding for GET messages/{message_id}
2 participants