apiaryio / snowcrash

API Blueprint Parser
MIT License
374 stars 56 forks source link

Drafter - snowcrash parser harness #57

Closed ecordell closed 9 years ago

ecordell commented 10 years ago

In the interest of keeping documentation up-to-date, it seems you would want to get warnings when your example Body doesn't fit your Schema.

Maybe this issue should be for a different project...it's definitely something snowcrash could handle, but perhaps it shouldn't have a dependency on json schema?

kuba-kubula commented 10 years ago

@ecordell I understand Snow Crash as a parser, with output containing AST and (semantical or informal) warnings.

And it won't work, because you want the Schema to be in human-readable form with indentation and prettified - not one-liner, right? I think this will bring much much much more complexity to Snow Crash, than we need right now.

Probably other tools can handle Schema, and its validation of example Body. E.g. https://github.com/Baggz/Amanda is a great tool for that.

ecordell commented 10 years ago

Personally, this is functionality that I want in Dredd.

Dredd generates API tests from a blueprint file. -- it uses Gavel to compare real responses with expected responses (with amanda, and a json schema). -- it uses Protagonist, which wraps snowcrash, to parse the blueprint into AST.

The argument for putting it in Gavel would be that Gavel already has the ability to check a json object against a schema, so it should be fairly easy to add.

But there are some good arguments for putting it in Protagonist (and perhaps even Snowcrash) as well:

And against:

Anyway...maybe Gavel is the best place for this. But I think it's a useful thing to add somewhere in the toolchain, yes?

zdne commented 10 years ago

@ecordell

While I believe this might be super handy I currently pivot Snow Crash as media-type independent. For Snow Crash an asset is an opaque structure. Which is also one of the reasons I am hesitating with the possibility to reference models inside an asset #33

Also note that I still believe that it should be perfectly OK to write blueprints with partially or completely undefined assets:

# GET /resource
+ Response 200 (application/json)

        { ... }

Which comes really handy when you are making sketches of your API.

Now with that being said I do not want to neglect on the benefits of having the checks there at a compile time. While this one particular request might be covered in Gavel I am playing with the idea of building whole Snow Crash compiler infrastructure, or harness if you will. That will basically take care of task like Modularity, Data Dictionaries, Embedded Assets and perhaps this one as well.

This infrastructure could be possible written in a scripting language using Snow Crash through a binding.

Also note, that while I do not have the control over it, I would suggest bindings not to add functionality to over the Snow Crash except for what is needed to accommodate given's language habits.

ecordell commented 10 years ago

I've been thinking more about this. First, I think you make really good points and this functionality shouldn't really be a part of snowcrash (didn't really think it should be, just thought posting here would help suggest where it should go). But more than that, I've been thinking this sort of thing deserves its own separate tool.

building whole Snow Crash compiler infrastructure, or harness if you will.

There are a few things that I've come across in managing a fairly large API that are starting to seem more necessary than simply "nice to have":

I see all of these as related structural and stylistic checks. They don't really seem to fit into the Snowcrash -> Protagonist -> Gavel -> Dredd toolchain (or any equivalent toolchain in some other language). I was thinking a single tool could handle all of these tasks (perhaps). Or maybe two tools, one to handle the structural issues and one to handle the stylistic/data integrity issues.

Consider this file structure:

blueprint.md //generated dynamically
blueprint.config
/parts
  intro.md
  users.md
  posts.md
/dictionaries
  user.json
  user.schema.json
  userCollection.json

The tool would:

  1. Build blueprint.md from the files in /parts and /dictionaries. This step covers Modularity, Data Dictionaries, and Embedded Assets.
  2. Parse the blueprint into AST
  3. Walk the AST and lint/validate payloads, outputting warnings/errors.
  4. If AST is valid (or has only warnings), read from blueprint.config and determine how to generate the blueprint
  5. Generate blueprint.md (again, overwriting existing blueprint with linted/validated version)
  6. (Pass blueprint.md to any other tool for using blueprints)

It should be pretty clear how to split this into a tool that handles step 1 and a tool that handles 2-6. I should add that all of this should be configurable in some way, so that, for example, if you want example json responses that are invalid, you don't have to have them pass a validation.

Does this in any way line up with your thoughts? Or did you have something completely different in mind?

Edit: thinking about a tool workflow for something like this.

Let's tentatively call this tool drafter (because a drafter make blueprints!)

drafter build . --output=blueprint.md build a blueprint file from its parts.

drafter lint . --format=json lint json payloads

drafter validate . --format=json-schema validate payloads using schema

drafter lint blueprint.md --config=blueprint.config take an existing blueprint and lint it according to a configuration.

zdne commented 10 years ago

@ecordell

Hat tip Sir!

Including the drafter name! I think we are pretty much aligned here. Awesome you have put it into words. Thank you.

In regards of the API Blueprint linter – I am a little bit confused here. Are you calling for a tool that validates a blueprint? snowcrash --validate can already do this. Why do you want to employ any sort of AST to markdown tool in lint-ing a blueprint?

However note that a such a tool AST media-type to blueprint is coming very soon for various other reasons. I do plan to provide it in the course of next month as a ruby gem.

Another part of your harness I am not sure about is the purpose of the config file? Is this to drive the linting during the build phase, e.g. to decide whether or not to validate some assets? Would you care to provide a draft of this file?

Also are you OK to rename this Issue to something like "Drafter – Snow Crash parser harness" or something more descriptive to where we are now?

Great stuff! Thanks!

ecordell commented 10 years ago

Hat tip Sir!

Including the drafter name! I think we are pretty much aligned here. Awesome you have put it into words. Thank you.

Thanks! Glad we're on the same page.

In regards of the API Blueprint linter – I am a little bit confused here. Are you calling for a tool that validates a blueprint? snowcrash --validate can already do this. Why do you want to employ any sort of AST to markdown tool in lint-ing a blueprint? Another part of your harness I am not sure about is the purpose of the config file? Is this to drive the linting during the build phase, e.g. to decide whether or not to validate some assets? Would you care to provide a draft of this file?

Maybe linter is a bad name for it. I was just thinking about a tool that could take a (valid) blueprint file as an input that had several of different styles in it (maybe some resources are nested like the Gist Fox example, and others write out the URI for every single request) and outputs a blueprint file with a unified style and spacing.

I think that part of the tool is the least important, but if it's still confusing I can try to explain with examples. The config file would just be to control how that output looks. Maybe it would be better to just have a dumber drafter format command that could fix spacing issues. (Something like this but for blueprint). If that were combined with the functionality snowcrash --validate already gives you, then you'd have a tool for blueprints that does exactly what lint tools do.

Drafter Build

For me, right now, the most important and pressing need is a tool to do what I listed as step 1: combining multiple files into one blueprint. I've been mulling this over a bit more, and I think I can outline a solution that solves this problem, while keeping in mind these other issues.

This is probably best described through an example. Consider the following file structure:

blueprint.md //output
blueprint.skeleton.md //input
gists/gist.json
gists/gistCollection.json

blueprint.skeleton.md

# Group Gist
Gist-related resources of *Gist Fox API*.

## Gist [/gists/{id}]

+ Parameters
    + id (string) ... ID of the Gist in the form of a hash.

### Retrieve a Single Gist [GET]
+ Response 200

     [](gists/gist.json)

### Edit a Gist [PATCH]
+ Request (application/json)

        {
            "content": "Updated file contents"
        }

+ Response 200

    [](gists/gist.json)

### Delete a Gist [DELETE]
+ Response 204

## Gists Collection [/gists{?since}]
Collection of all Gists.

### List All Gists [GET]
+ Parameters
    + since (optional, string) ... Timestamp in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ` Only gists updated at or after this time are returned.

+ Response 200

    [](gists/gistCollection.json)

### Create a Gist [POST]

+ Request (application/json)

        {
            "description": "Description of Gist",
            "content": "String content"
        }

+ Response 201

     [](gists/gist.json)

gists/gist.json

{
    "_links": {
        "self": { "href": "/gists/42" },
        "star": { "href": "/gists/42/star" },
    },
    "id": "42",
    "created_at": "2014-04-14T02:15:15Z",
    "description": "Description of Gist",
    "content": "String contents"
}

gists/gistCollection.json

{
    "_links": {
        "self": { "href": "/gists" }
    },
    "_embedded": {
        "gists": [
            {
                "_links" : {
                    "self": { "href": "/gists/42" }
                },
                "id": "42",
                "created_at": "2014-04-14T02:15:15Z",
                "description": "Description of Gist"
            }
        ]
    },
    "total": 1
}

Then, you would run this command:

drafter build --input=blueprint.skeleton.md --output=blueprint.md

This would simply scan the document for [](file) links, and then replace them (being careful to keep indentation) with the contents of the linked file. Empty text for the link would signal to include rather than simply link.

Or, perhaps the syntax would be something like :[file](file), so that one could parse blueprint.skeleton.md with a markdown parser, and get links to the files. (The : would tell snowcrash to embed the resource)

The output:

blueprint.md

# Group Gist
Gist-related resources of *Gist Fox API*.

## Gist [/gists/{id}]

+ Parameters
    + id (string) ... ID of the Gist in the form of a hash.

### Retrieve a Single Gist [GET]
+ Response 200

       {
            "_links": {
                "self": { "href": "/gists/42" },
                "star": { "href": "/gists/42/star" },
            },
            "id": "42",
            "created_at": "2014-04-14T02:15:15Z",
            "description": "Description of Gist",
            "content": "String contents"
        }

### Edit a Gist [PATCH]
+ Request (application/json)

        {
            "content": "Updated file contents"
        }

+ Response 200

       {
            "_links": {
                "self": { "href": "/gists/42" },
                "star": { "href": "/gists/42/star" },
            },
            "id": "42",
            "created_at": "2014-04-14T02:15:15Z",
            "description": "Description of Gist",
            "content": "String contents"
        }

### Delete a Gist [DELETE]
+ Response 204

## Gists Collection [/gists{?since}]
Collection of all Gists.

### List All Gists [GET]
+ Parameters
    + since (optional, string) ... Timestamp in ISO 8601 format: `YYYY-MM-DDTHH:MM:SSZ` Only gists updated at or after this time are returned.

+ Response 200

            {
                "_links": {
                    "self": { "href": "/gists" }
                },
                "_embedded": {
                    "gists": [
                        {
                            "_links" : {
                                "self": { "href": "/gists/42" }
                            },
                            "id": "42",
                            "created_at": "2014-04-14T02:15:15Z",
                            "description": "Description of Gist"
                        }
                    ]
                },
                "total": 1
            }

### Create a Gist [POST]

+ Request (application/json)

        {
            "description": "Description of Gist",
            "content": "String content"
        }

+ Response 201

       {
            "_links": {
                "self": { "href": "/gists/42" },
                "star": { "href": "/gists/42/star" },
            },
            "id": "42",
            "created_at": "2014-04-14T02:15:15Z",
            "description": "Description of Gist",
            "content": "String contents"
        }

That would be fairly simple to achieve (unless I'm missing something, that could be handled with some simple regex). This has a few advantages over my previous proposal:

Thoughts?

(sorry this is so lengthy!)

zdne commented 10 years ago

@ecordell

Thanks for explanation on the linter. So essentially it would be a pretty-printer + validator, same as jsonlint.com is.

  1. Parse (thus validate) API Blueprint into a serialized AST media-type
  2. Reconstruct a canonical version API Blueprint back from the serialized AST media-type

Makes sense.

Since part 1 is already done (snowcrash command line tool). It is really matter of writing the tool (ruby gem?) to convert a serialized AST to Markdown and put those tow at work together.

Now to the juicy part - Drafter.

First my thoughts about referencing external assets: There is already some initial concept here apiaryio/api-blueprint#20 I would add & build on it. So the "template" blueprint is actually a real API Blueprint.

I would propose following:

Referencing an external asset:

[file.json](path/to/file.json)

Embedding an external asset:

![file.json](path/to/file.json)

(this is Markdown syntax for embedding pictures)

In the first case (referencing) the parser would just add the reference to a (new) href key in AST. In the later, the parser harness would fetch & embed the asset from the path or URL into the AST.

Does this makes sense?


Note: I still want to comment on the command line arguments and the config file, but I will do it in another post for the sake of clarity

zdne commented 10 years ago

Also what we are missing here is the part when you break up a blueprint into multiple files. I can see two approaches here:

  1. "Include" (require) other blueprints from within a blueprint (possibly in a metadata section at the top of a blueprint)
  2. Drive this using the some sort of config file that would specify what directories or files should be used to stitch the final blueprint

What do you think?

BRMatt commented 10 years ago

(this is Markdown syntax for embedding pictures)

Wouldn't this prevent someone from embedding images in their documentation?

Perhaps =[file.json](path/to/file.json) would be better, as viewing documentation on github wouldn't cause it to try and render json as an image. On 15 Nov 2013 00:39, "Z" notifications@github.com wrote:

@ecordell https://github.com/ecordell

Thanks for explanation on the linter. So essentially it would be a pretty-printer + validator, same as jsonlint.com is.

  1. Parse (thus validate) API Blueprint into a serialized AST media-type
  2. Reconstruct a canonical version API Blueprint back from the serialized AST media-type

Makes sense.

Since part 1 is already done (snowcrash command line tool). It is really matter of writing the tool (ruby gem?) to convert a serialized AST to Markdown and put those tow at work together.

Now to the juicy part - Drafter.

First my thoughts about referencing external assets: There is already some initial concept here apiaryio/api-blueprint#20https://github.com/apiaryio/api-blueprint/issues/20I would add & build on it. So the "template" blueprint is actually a real API Blueprint.

I would propose following:

Referencing an external asset:

file.json

Embedding an external asset:

file.json

(this is Markdown syntax for embedding pictures)

In the first case (referencing) the parser would just add the reference to a (new) href key in AST. In the later, the parser harness would fetch & embed the asset from the path or URL into the AST.

Does this makes sense?

Note: I still want to comment on the command line arguments and the config file, but I will do it in another post for the sake of clarity

— Reply to this email directly or view it on GitHubhttps://github.com/apiaryio/snowcrash/issues/57#issuecomment-28538021 .

zdne commented 10 years ago

@BRMatt

Wouldn't this prevent someone from embedding images in their documentation?

It shouldn't the drafter should know when to replace it and when not from the context. But

on github wouldn't cause it to try and render json as an image.

This is a fair point. So apparently we must come with something as =[file.json](path/to/file.json) or :[file.json](path/to/file.json) as @ecordell suggests.

Thanks!

zdne commented 10 years ago

Revisiting API Blueprint Plans & Issues) I have stumbled upon on yet still undecided Modularity issue – apiaryio/api-blueprint#8 – breaking a blueprint into multiple file.

Please feel free to add any thoughts on this here or directly at apiaryio/api-blueprint#8

zdne commented 9 years ago

We have kicked-off the Drafter project. Initially it will provide expansion of MSON data structures (types) and rendering of representations out of it. Support for additional features is in the pipeline.

If needed please continue with this discussion at https://github.com/apiaryio/drafter/issues/31

ecordell commented 9 years ago

@zdne Looks great! Can't wait to see what happens. If I manage to find some time I'd love to contribute.

zdne commented 9 years ago

Thanks @ecordell ! At the moment it is just a simple wrapper but the real juice is coming soon!