Drafter - snowcrash parser harness
ecordell opened this issue · 14 comments
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?
@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.
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:
- It's an error that can be discovered at compile time of the blueprint, and it's almost always good to move as many errors to that stage as possible.
- When running API tests, you expect a test to fail because the response didn't match what you specified, not because you documented something differently in two places.
And against:
- It definitely feels like bloat.
- Maybe you actually do expect tests to fail because you documented something differently in two places.
Anyway...maybe Gavel is the best place for this. But I think it's a useful thing to add somewhere in the toolchain, yes?
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.
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":
- Ability to split the API across multiple files
- This is your "Modularity". Our blueprint is on the order of 5k lines and is difficult to navigate, and it will probably double or triple in length when I add schemas. Also, apiary.io errors frequently with a file of this size, and can't keep up with parsing/previewing/validating.
- Ability to store payloads separately from the blueprint itself
- Your "Data Dictionaries" idea
- Ability to build payloads from other payloads
- Your "Embedded Assets" idea. The concept exists in json schema and it would be nice to share it with json/other formats.
- Ability to validate JSON from JSON schema
- Plenty of tools to do this exist
- Ability to lint JSON and JSON schema
- Plenty of tools to do this exist
- Ability to generate JSON schema from JSON
- This exists in Gavel already, but can't be used for writing back to the blueprint.
- Ability to lint Blueprint
- I'm thinking a basic reverse-parsing, converting the AST back into a blueprint. This would be straightforward if there weren't so many options for how to write a Blueprint document. As it stands I think there would need to be a fairly complex config file or something to instruct the linter how to output the document.
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:
- Build blueprint.md from the files in /parts and /dictionaries. This step covers Modularity, Data Dictionaries, and Embedded Assets.
- Parse the blueprint into AST
- Walk the AST and lint/validate payloads, outputting warnings/errors.
- If AST is valid (or has only warnings), read from blueprint.config and determine how to generate the blueprint
- Generate blueprint.md (again, overwriting existing blueprint with linted/validated version)
- (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.
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!
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:
- It doesn't depend on particular file naming/folder structure.
- There is far more control over what the resulting blueprint file looks like (thanks to blueprint.skeleton.md)
- Further, there's no restrictions on what can go in a skeleton file, so you can refactor a large blueprint one piece at a time without affecting the generated blueprint.
- It could be extended to allow embedding assets (e.g. gistsCollection.json has references to gist.json)
- This requires more work to avoid circular references.
- The embedding resources issue bothers me because the options seem to be: have a different method for each media type (so that the assets themselves are still valid in their language pre parsing), or have a single method for embedding, but which makes the assets, on their own invalid. Also, some media types (json schema comes to mind) already have mechanisms for this.
Thoughts?
(sorry this is so lengthy!)
Thanks for explanation on the linter. So essentially it would be a pretty-printer + validator, same as jsonlint.com is.
- Parse (thus validate) API Blueprint into a serialized AST media-type
- 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
Also what we are missing here is the part when you break up a blueprint into multiple files. I can see two approaches here:
- "Include" (require) other blueprints from within a blueprint (possibly in a metadata section at the top of a blueprint)
- 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?
(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.
- Parse (thus validate) API Blueprint into a serialized AST media-type
- Reconstruct a canonical version API Blueprint back from the
serialized AST media-typeMakes 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:
Embedding an external asset:
(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//issues/57#issuecomment-28538021
.
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!
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
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 apiaryio/drafter#31