Skip to content

Descriptions as Strings in BuildFromDefinition #1167

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

Closed
cjoudrey opened this issue Dec 2, 2017 · 14 comments
Closed

Descriptions as Strings in BuildFromDefinition #1167

cjoudrey opened this issue Dec 2, 2017 · 14 comments

Comments

@cjoudrey
Copy link
Contributor

cjoudrey commented Dec 2, 2017

I just stumbled upon this PR in graphql-js: graphql/graphql-js#927

This proposes replacing leading comment blocks as descriptions in the schema definition language with leading strings.

Before

# This is a description
# of the `Foo` type.

type Foo implements Bar {
  one: Type
}

Now

"""
This is a description
of the `Foo` type.
"""
type Foo implements Bar {
  one: Type
}

Implementing this depends on #1150.

Ref. Impl.:

Spec: graphql/graphql-spec#90

@rmosolgo
Copy link
Owner

rmosolgo commented Dec 2, 2017

👍 For adding it, but we shouldn't remove the old way yet. If anyone is depending on it, we want to give them a chance to update first.

@cjoudrey
Copy link
Contributor Author

cjoudrey commented Dec 3, 2017

Yeah, I agree. We should be able to support both ways if my memory of that code is right.

@zzak
Copy link

zzak commented Mar 18, 2018

I'd take a stab at this, but seems BLOCK_STRING already uses this token. So adding it would break compatibility with existing documents?

Any ideas @rmosolgo? 🙇

@rmosolgo
Copy link
Owner

So far, we only have the first part of block string support: we added it to lexer.rl, and it emits string tokens, so you can use block strings for query inputs.

But we need to use BLOCK_STRING for a second purpose: In the Schema Definition Language, descriptions may be expressed with leading block strings. Our implementation is outdated, from when the SDL was a draft. It uses preceding comment lines to build descriptions and pass them to described items (eg field, object type.

Parser-wise, what we need to do is:

  • Allow string literals to come before definitions in the SDL
  • Use those string literals as descriptions
  • AND continue to support comment descriptions if strings are not present, for compatibility

Additionally, we need to update Schema::Printer to output descriptions in that format, with leading strings instead of comments.

Does that clarify it a bit?

  • Extend the parser to allow

@zzak
Copy link

zzak commented Mar 18, 2018

@rmosolgo Thanks for your reply!

I was wondering if we could re-use the :COMMENT token that are generated by record_comment, and then scan for a similar block string that comes before a defition. For that we would have to peek? Or we could combine definitions to a catch-all?

I'm not super familiar with ragel, but seems pretty straight forward.

@rmosolgo
Copy link
Owner

Personally, I would rather not reuse that that code because it's a bit of a hack. Comments are technically ignored tokens, so they're ignored by the parser entirely. However, since the first draft of the SDL said that comments could be used for description, we supported that (#305) with record_comment, which required making tokens into a doubly-linked list. That way, they could remain ignored by the parser, but we could still fetch descriptions by backtracking through the list of tokens.

I think if we can implement descriptions at the parser level, we could simplify the code a bit. For example, if we picked up leading strings like:

diff --git a/lib/graphql/language/parser.y b/lib/graphql/language/parser.y
index 071e57c5..65c6c9b9 100644
--- a/lib/graphql/language/parser.y
+++ b/lib/graphql/language/parser.y
@@ -295,9 +295,13 @@ rule

+  description_opt:
+      /* none */ { return nil }
+    | STRING
+
   object_type_definition:
-      TYPE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY {
-        return make_node(:ObjectTypeDefinition, name: val[1], interfaces: val[2], directives: val[3], fields: val[5], description: get_description(val[0]), position_source: val[0])
+      description_opt TYPE name implements_opt directives_list_opt LCURLY field_definition_list RCURLY {
+        return make_node(:ObjectTypeDefinition, name: val[2], interfaces: val[3], directives: val[4], fields: val[6], description: (val[0] || get_description(val[1])), position_source: val[0] || val[1])
       }

Then (after a period of backward compat), we could:

  • remove get_description and record_comment
  • stop keeping tokens as a doubly-linked-list
  • start ignoring tokens altogether (make the Ragel action a no-op)

So, although it's a bigger change now, I think it sets us up for a bit simpler system down the road.

That said, beggars can't be choosers: if you want to send a PR with the implementation you mentioned and a few tests, I'd happily accept it as a solid improvement, and then later, when we remove the comments-as-descriptions code, we can revisit those changes to the parser!

@zzak
Copy link

zzak commented Mar 19, 2018

@rmosolgo Thank you for your explanation!

I tried to follow your instruction for description_opt, but found I was not able to retrieve the previous token as STRING inside get_description. With a bit more prodding, I might be able to figure it out -- but for now I've had to time box this to a days worth of work.

We'll have to come back to this, so I don't want to hold things up if someone else gets to it first.

My impression after this though, is that your original suggestion should work -- and we can keep compatibility with comment style descriptions.

@rmosolgo
Copy link
Owner

Huh, thanks for taking a stab at it! I took a little try with my pseudocode from above:

11c1bf7

In the test case added there, we never enter get_description, since val[0] (which is type_definition_opt) is present. I'm hoping to bypass get_description altogether since it's a bit of weird code which was required to support comments-as-descriptions, but not strings-as-descriptions 😅

@zzak
Copy link

zzak commented Mar 19, 2018

@rmosolgo Oh interesting, that is not far off from the patch I was working on. And the tests pass!

How about for a single-line string?

Such as:

  it "parses strings as type descriptions" do
    defn_string = <<-GRAPHQL
    """Here's a description"""
    type Thing { field: Stuff }
    GRAPHQL

    document = subject.parse(defn_string)
    type_defn = document.definitions.first
    assert_equal "Thing", type_defn.name
    assert_equal "Here's a description", type_defn.description
  end

@rmosolgo
Copy link
Owner

According to the spec, any string should be fair game. A type's description is a StringValue (src):

Description : StringValue

and a StringValue is either a "single-quoted string" or a """block string""" (src:

StringValue ::

  • " StringCharacter* "
  • """ BlockStringCharacter* """

So, an example like the one you posted should be supported!

@rmosolgo
Copy link
Owner

Oh, maybe I misread, your example was a block string, but it didn't begin with a newline. This is also fair game, for example, here's a block string """Cheese""": https://github.com/rmosolgo/graphql-ruby/pull/1219/files#diff-a15bdba7df1e7fbfa7e99d733d46be17L88

@zzak
Copy link

zzak commented Mar 19, 2018

@rmosolgo That's exactly what I meant, using a block string for the description.

Both seem at least included in the spec and other implementations I've come across.

@daemonsy
Copy link
Contributor

daemonsy commented Jun 8, 2019

Hi, I'm helping to close out issues. From reading the discussion, it seems this is fully resolved when #1725 landed.

I'm closing the issue, let me know if anyone likes to reopen it.

@daemonsy daemonsy closed this as completed Jun 8, 2019
@zzak
Copy link

zzak commented Jun 22, 2019

Glad to see this land, nice work! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants