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

yaml: implement basic Swagger/OpenAPI subparser #3245

Closed
wants to merge 16 commits into from

Conversation

segoon
Copy link

@segoon segoon commented Dec 31, 2021

The parser is able to parse both Swagger 2.0 and Openapi 3.0.x.
The code is partly based on ansible playbook parser. It was tested
using the following vim's TagBar parser declaration:

let g:tagbar_type_yaml = {
                    \  'ctagstype': 'openapi',
                    \  'kinds': [
                    \          'p:path',
                    \          'd:schema',
                    \          'P:parameter',
                    \          'R:response',
                    \          ]
                    \ }

*
* This source code is released for free distribution under the terms of the
* GNU General Public License version 2 or (at your option) any later version.
*
Copy link
Member

Choose a reason for hiding this comment

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

Could you add references for the target language or input like:

* GDScript language reference:

git push --force is welcome.

@codecov
Copy link

codecov bot commented Dec 31, 2021

Codecov Report

Merging #3245 (370796b) into master (8215492) will increase coverage by 0.32%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3245      +/-   ##
==========================================
+ Coverage   84.98%   85.31%   +0.32%     
==========================================
  Files         207      208       +1     
  Lines       49324    49525     +201     
==========================================
+ Hits        41916    42250     +334     
+ Misses       7408     7275     -133     
Impacted Files Coverage Δ
parsers/openapi.c 100.00% <100.00%> (ø)
parsers/rst.c 92.98% <0.00%> (-6.33%) ⬇️
main/lregex.c 82.07% <0.00%> (-1.13%) ⬇️
main/field.c 92.73% <0.00%> (-0.29%) ⬇️
optlib/markdown.c
parsers/markdown.c 100.00% <0.00%> (ø)
parsers/html.c 99.25% <0.00%> (+<0.01%) ⬆️
parsers/ada.c 87.21% <0.00%> (+0.06%) ⬆️
parsers/rust.c 92.16% <0.00%> (+0.11%) ⬆️
parsers/basic.c 99.03% <0.00%> (+0.55%) ⬆️
... 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 8215492...370796b. Read the comment docs.

@masatake
Copy link
Member

Writing a subparser on the YAML parser is an incredible challenge!
Let's jump into the wild area.

@masatake
Copy link
Member

Is there any difference between the targets, Swagger 2.0 and Openapi 3.0.x?

The parser is able to parse both Swagger 2.0 and Openapi 3.0.x.
The code is partly based on ansible playbook parser.  It was tested
using the following vim's TagBar parser declaration:

    let g:tagbar_type_yaml = {
                        \  'ctagstype': 'openapi',
                        \  'kinds': [
                        \          'p:path',
                        \          'd:schema',
                        \          'P:parameter',
                        \          'R:response',
                        \          ]
                        \ }
KEY_PATHS,
};

static const enum openapiKeys responses3Keys[] = {
Copy link
Author

Choose a reason for hiding this comment

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

Is there any difference between the targets, Swagger 2.0 and Openapi 3.0.x?

They are 2 different revisions of the protocol (Swagger == OpenAPI 2.0). They are mostly the same except for several details (not interesting for ctags) and several sections have a distinct path in yaml (e.g. "responses" in swagger and "components.responses" for openapi). I tried to express it in XXXKeys names with 2 (swagger) or 3 (openapi). pathKeys is the same for both.

Copy link
Member

Choose a reason for hiding this comment

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

I see. Thank you.

swagger: '2.0'
info:
description: test
title: test
Copy link
Member

Choose a reason for hiding this comment

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

How about tagging the title?

Copy link
Author

@segoon segoon Jan 2, 2022

Choose a reason for hiding this comment

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

If it makes sense to tag all user-defined entities, then ok.
But then more fields must be tagged:

  • info itself
  • servers children
  • webhooks children
  • security children
  • tags children

I don't think the following entities worth adding tags (too minor IMHO):

  • openapi
  • jsonSchemaDialect
  • externalDocs

https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object

What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

added

Copy link
Member

Choose a reason for hiding this comment

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

The way I wrote is wrong.
In 97915e7, your parser extracts "title".
However, what I expected is your parser extract "test" in "title: test".

version: '1.0'

servers:
- url: http://example.com
Copy link
Member

Choose a reason for hiding this comment

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

How about tagging the URLs under servers?

@@ -0,0 +1,55 @@
openapi: 3.0.0
info:
title: test
Copy link
Member

Choose a reason for hiding this comment

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

How about tagging the title?

{ DEPTYPE_SUBPARSER, "Yaml", &openapiSubparser },
};

parserDefinition* const def = parserNew ("OpenApi");
Copy link
Member

Choose a reason for hiding this comment

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

It seems that OpenAPI may be better than OpenApi.

Copy link
Author

Choose a reason for hiding this comment

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

ok, no problem

Copy link
Author

Choose a reason for hiding this comment

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

renamed

@masatake
Copy link
Member

masatake commented Jan 2, 2022

Though I named the yaml parser "Yaml", I think "OpenAPI" is better than "OpenApi" as the name of your parser.

(The review is in progress.)

def->kindTable = OpenApiKinds;
def->kindCount = ARRAY_SIZE (OpenApiKinds);
def->parser = findOpenApiTags;
def->useCork = CORK_NIL;
Copy link
Member

Choose a reason for hiding this comment

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

CORK_NIL is not suitable value for useCork field.
The useCork field is initialized with 0 in parserNew. So you don't have to give a value to the field as far as your parser doesn't need cork indexes. Let's remove this line.

Copy link
Author

Choose a reason for hiding this comment

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

oh, fixed

return def;
}

/* vi:set tabstop=4 shiftwidth=4: */
Copy link
Member

Choose a reason for hiding this comment

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

Is .editorconfig not enough?

Copy link
Author

Choose a reason for hiding this comment

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

it was a copy-pasted line from ansible parser, removed

Copy link
Member

Choose a reason for hiding this comment

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

Oh, sorry, I wonder where it came from because I don't use vim:-).

yaml_token_t *token)
{
int i;
for (i = 0; i < ARRAY_SIZE(tagSources); i++) {
Copy link
Member

Choose a reason for hiding this comment

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

Will you maintain this parser?
If the answer is "yes", you can choose your favorite C coding style.
If the answer is "no", I would like you to follow the ctags' coding style like:

   for (i = 0; i < ARRAY_SIZE(tagSources); i++)
   {
      ...

See the position of {.

Copy link
Author

Choose a reason for hiding this comment

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

fixed

openapi->type_stack,
ts->keys,
ts->countKeys
)) {
Copy link
Member

Choose a reason for hiding this comment

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

Impressive technique. I must rewrite the ansibleplaybook.c with this technique.

Copy link
Author

Choose a reason for hiding this comment

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

thanks :)


static void inputEnd(subparser *s)
{
Assert (((struct sOpenApiSubparser*)s)->type_stack == NULL);
Copy link
Member

Choose a reason for hiding this comment

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

Even if a broken input is given to the parser, we can expect the type_stack becomes empty at the end of parsing?

If we cannot expect it, we should clear the stack here instead of doing "Assert".

Copy link
Author

Choose a reason for hiding this comment

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

ok, done


static enum openapiKeys parseKey(yaml_token_t *token)
{
if (scalarNeq(token, 10, "components"))
Copy link
Member

Choose a reason for hiding this comment

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

For mapping from "string" to a key, you can use functions defined in main/keyword.h.
I write this here is not for performance. With the function, the code of this parser can be more declarative.

@masatake
Copy link
Member

masatake commented Jan 5, 2022

This parser is really nice. The technique used in this parser excites me.

In past years, I have been thinking about YPATH, something like XPath but for YAML.
However, I cannot find the time and the way how to implement such a thing.
This parser illustrates the way.
Though the code in this pull request is for OpenAPI. However, most part of the code can be moved to yaml.c/yaml.h.
It will make developing a subparser on the YAML parser much easier.

I'm reading openapi spec slowly. What should be tagged or should not be tagged is not so obvious to me.
It will take more time.

@masatake
Copy link
Member

masatake commented Jan 5, 2022

@segoon, thank you for updating.
I'm not good at English. So I converted what I wrote here as review comments to C language. See #3254.
It seems that the many parts of #3254 are already included in the updating.

@masatake
Copy link
Member

masatake commented Jan 5, 2022

handleKey and handleValue do the same with different tables.
How about merging them like:

diff --git a/parsers/openapi.c b/parsers/openapi.c
index 9e26aecdf..493f32ed1 100644
--- a/parsers/openapi.c
+++ b/parsers/openapi.c
@@ -447,13 +447,15 @@ const struct tagSource tagValueSources[] = {
    },
 };
 
-static void handleKey(struct sOpenApiSubparser *openapi,
-                                            yaml_token_t *token)
+static void handleToken(struct sOpenApiSubparser *openapi,
+                       yaml_token_t *token,
+                       const struct tagSource  tss[],
+                       size_t ts_count)
 {
    int i;
-   for (i = 0; i < ARRAY_SIZE(tagSources); i++)
+   for (i = 0; i < ts_count; i++)
    {
-       const struct tagSource* ts = &tagSources[i];
+       const struct tagSource* ts = &tss[i];
 
        if (stateStackMatch(
                    openapi->type_stack,
@@ -473,31 +475,17 @@ static void handleKey(struct sOpenApiSubparser *openapi,
    }
 }
 
+static void handleKey(struct sOpenApiSubparser *openapi,
+                                            yaml_token_t *token)
+{
+   handleToken (openapi, token, tagSources, ARRAY_SIZE(tagSources));
+}
+
 
 static void handleValue(struct sOpenApiSubparser *openapi,
                        yaml_token_t *token)
 {
-   int i;
-   for (i = 0; i < ARRAY_SIZE(tagValueSources); i++)
-   {
-       const struct tagSource* ts = &tagValueSources[i];
-
-       if (stateStackMatch(
-                   openapi->type_stack,
-                   ts->keys,
-                   ts->countKeys
-           ))
-       {
-           // printf("match value! i=%d\n", i);
-
-           tagEntryInfo tag;
-           initTagEntry (&tag, (char *)token->data.scalar.value, ts->kind);
-           attachYamlPosition (&tag, token, false);
-
-           makeTagEntry (&tag);
-           break;
-       }
-   }
+   handleToken (openapi, token, tagValueSources, ARRAY_SIZE(tagValueSources));
 }
 
 static void openapiPlayStateMachine (struct sOpenApiSubparser *openapi,

@masatake
Copy link
Member

masatake commented Jan 5, 2022

I am sorry for not providing the details all at once.
How do you think about #3254 ?
If you are o.k., I will merge #3254.
We can polish the parser after merging it.

@segoon
Copy link
Author

segoon commented Jan 6, 2022

I'm OK with that.

masatake added a commit that referenced this pull request Jan 6, 2022
yaml: implement basic Swagger/OpenAPI subparser
@masatake
Copy link
Member

masatake commented Jan 6, 2022

#3254 is merged. Thank you.
Could you make a new pull request for the improvements?

@masatake masatake closed this Jan 6, 2022
@segoon
Copy link
Author

segoon commented Jan 6, 2022

here: #3258

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

Successfully merging this pull request may close these issues.

2 participants