@api-platform/api-doc-parser
Advanced tools
Comparing version 0.5.0 to 0.6.0
@@ -44,2 +44,6 @@ "use strict"; | ||
var _addParameters = require("./addParameters"); | ||
var _addParameters2 = _interopRequireDefault(_addParameters); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -444,3 +448,4 @@ | ||
operations: resourceOperations, | ||
deprecated: (0, _lodash2.default)(relatedClass, '["http://www.w3.org/2002/07/owl#deprecated"][0]["@value"]', false) | ||
deprecated: (0, _lodash2.default)(relatedClass, '["http://www.w3.org/2002/07/owl#deprecated"][0]["@value"]', false), | ||
parameters: [] | ||
})); | ||
@@ -503,10 +508,17 @@ } | ||
}); | ||
}, function (_ref2) { | ||
var response = _ref2.response; | ||
}, function (data) { | ||
return _promise2.default.reject({ | ||
api: new _Api2.default(entrypointUrl, { resources: [] }), | ||
response: response, | ||
status: (0, _lodash2.default)(response, "status") | ||
error: data, | ||
response: data.response, | ||
status: (0, _lodash2.default)(data.response, "status") | ||
}); | ||
}).then(function (_ref2) { | ||
var api = _ref2.api, | ||
response = _ref2.response, | ||
status = _ref2.status; | ||
return (0, _addParameters2.default)(api).then(function (api) { | ||
return { api: api, response: response, status: status }; | ||
}); | ||
}); | ||
} |
@@ -27,2 +27,6 @@ "use strict"; | ||
var _Parameter = require("./Parameter"); | ||
var _Parameter2 = _interopRequireDefault(_Parameter); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -29,0 +33,0 @@ |
{ | ||
"name": "@api-platform/api-doc-parser", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Transform a Hydra API documentation in an intermediate representation that can be used for various tasks such as creating smart API clients, scaffolding code or building admininistration interfaces.", | ||
@@ -32,3 +32,3 @@ "files": [ | ||
"flow-bin": "^0.42.0", | ||
"jest": "^20.0.0", | ||
"jest": "^23.0.0", | ||
"jest-fetch-mock": "^1.0.8", | ||
@@ -47,3 +47,4 @@ "prettier": "^1.12.1" | ||
"eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check", | ||
"build": "babel src -d lib --ignore '*.test.js'" | ||
"build": "babel src -d lib --ignore '*.test.js'", | ||
"watch": "babel src -d lib --ignore '*.test.js' --watch" | ||
}, | ||
@@ -50,0 +51,0 @@ "jest": { |
@@ -7,3 +7,3 @@ # API Doc Parser | ||
`api-doc-parser` is a JavaScript (ES6) library to parse [Hydra](http://hydra-cg.com) API documentations and transform them | ||
`api-doc-parser` is a JavaScript (ES6) library to parse [Hydra](http://hydra-cg.com) or Swagger API documentations and transform them | ||
in an intermediate representation. This data structure can then be used for various tasks such as creating smart API clients, | ||
@@ -30,2 +30,3 @@ scaffolding code or building administration interfaces. | ||
**Hydra** | ||
```javascript | ||
@@ -37,6 +38,13 @@ import parseHydraDocumentation from 'api-doc-parser/lib/hydra/parseHydraDocumentation'; | ||
## Support for other formats (GraphQL, Swagger/OpenAPI, JSONAPI...) | ||
**Swagger** | ||
```javascript | ||
import parseSwaggerDocumentation from 'api-doc-parser/lib/swagger/parseSwaggerDocumentation'; | ||
parseSwaggerDocumentation('https://demo.api-platform.com/docs.json').then(({api}) => console.log(api)); | ||
``` | ||
## Support for other formats (GraphQL, JSONAPI...) | ||
API Doc Parser is designed to parse any API documentation format and convert it in the same intermediate representation. | ||
For now, only Hydra is supported but if you develop a parser for another format, please [open a Pull Request](https://github.com/dunglas/api-doc-parser/pulls) | ||
For now, only Hydra and Swagger is supported but if you develop a parser for another format, please [open a Pull Request](https://github.com/dunglas/api-doc-parser/pulls) | ||
to include it in the library. | ||
@@ -43,0 +51,0 @@ |
@@ -8,2 +8,3 @@ import { promises } from "jsonld"; | ||
import fetchJsonLd from "./fetchJsonLd"; | ||
import addParameters from "./addParameters"; | ||
@@ -194,125 +195,171 @@ /** | ||
return fetchEntrypointAndDocs(entrypointUrl, options).then( | ||
({ entrypoint, docs, response }) => { | ||
const resources = [], | ||
fields = [], | ||
operations = []; | ||
const title = get( | ||
docs, | ||
'[0]["http://www.w3.org/ns/hydra/core#title"][0]["@value"]', | ||
"API Platform" | ||
); | ||
const entrypointType = get(entrypoint, '[0]["@type"][0]'); | ||
if (!entrypointType) { | ||
throw new Error('The API entrypoint has no "@type" key.'); | ||
} | ||
const entrypointClass = findSupportedClass(docs, entrypointType); | ||
if ( | ||
!Array.isArray( | ||
entrypointClass["http://www.w3.org/ns/hydra/core#supportedProperty"] | ||
) | ||
) { | ||
throw new Error( | ||
'The entrypoint definition has no "http://www.w3.org/ns/hydra/core#supportedProperty" key or it is not an array.' | ||
return fetchEntrypointAndDocs(entrypointUrl, options) | ||
.then( | ||
({ entrypoint, docs, response }) => { | ||
const resources = [], | ||
fields = [], | ||
operations = []; | ||
const title = get( | ||
docs, | ||
'[0]["http://www.w3.org/ns/hydra/core#title"][0]["@value"]', | ||
"API Platform" | ||
); | ||
} | ||
// Add resources | ||
for (const properties of entrypointClass[ | ||
"http://www.w3.org/ns/hydra/core#supportedProperty" | ||
]) { | ||
const readableFields = [], | ||
resourceFields = [], | ||
writableFields = [], | ||
resourceOperations = []; | ||
const entrypointType = get(entrypoint, '[0]["@type"][0]'); | ||
if (!entrypointType) { | ||
throw new Error('The API entrypoint has no "@type" key.'); | ||
} | ||
const property = get( | ||
properties, | ||
'["http://www.w3.org/ns/hydra/core#property"][0]' | ||
); | ||
if (!property) { | ||
continue; | ||
const entrypointClass = findSupportedClass(docs, entrypointType); | ||
if ( | ||
!Array.isArray( | ||
entrypointClass["http://www.w3.org/ns/hydra/core#supportedProperty"] | ||
) | ||
) { | ||
throw new Error( | ||
'The entrypoint definition has no "http://www.w3.org/ns/hydra/core#supportedProperty" key or it is not an array.' | ||
); | ||
} | ||
// Add fields | ||
const relatedClass = findRelatedClass(docs, property); | ||
for (const supportedProperties of relatedClass[ | ||
// Add resources | ||
for (const properties of entrypointClass[ | ||
"http://www.w3.org/ns/hydra/core#supportedProperty" | ||
]) { | ||
const supportedProperty = get( | ||
supportedProperties, | ||
const readableFields = [], | ||
resourceFields = [], | ||
writableFields = [], | ||
resourceOperations = []; | ||
const property = get( | ||
properties, | ||
'["http://www.w3.org/ns/hydra/core#property"][0]' | ||
); | ||
const range = get( | ||
supportedProperty, | ||
'["http://www.w3.org/2000/01/rdf-schema#range"][0]["@id"]', | ||
null | ||
); | ||
if (!property) { | ||
continue; | ||
} | ||
const field = new Field( | ||
supportedProperty["http://www.w3.org/2000/01/rdf-schema#label"][0][ | ||
"@value" | ||
], | ||
{ | ||
id: supportedProperty["@id"], | ||
range: range, | ||
reference: | ||
"http://www.w3.org/ns/hydra/core#Link" === | ||
get(property, '["@type"][0]') | ||
? range | ||
: null, // Will be updated in a subsequent pass | ||
required: get( | ||
// Add fields | ||
const relatedClass = findRelatedClass(docs, property); | ||
for (const supportedProperties of relatedClass[ | ||
"http://www.w3.org/ns/hydra/core#supportedProperty" | ||
]) { | ||
const supportedProperty = get( | ||
supportedProperties, | ||
'["http://www.w3.org/ns/hydra/core#property"][0]' | ||
); | ||
const range = get( | ||
supportedProperty, | ||
'["http://www.w3.org/2000/01/rdf-schema#range"][0]["@id"]', | ||
null | ||
); | ||
const field = new Field( | ||
supportedProperty[ | ||
"http://www.w3.org/2000/01/rdf-schema#label" | ||
][0]["@value"], | ||
{ | ||
id: supportedProperty["@id"], | ||
range: range, | ||
reference: | ||
"http://www.w3.org/ns/hydra/core#Link" === | ||
get(property, '["@type"][0]') | ||
? range | ||
: null, // Will be updated in a subsequent pass | ||
required: get( | ||
supportedProperties, | ||
'["http://www.w3.org/ns/hydra/core#required"][0]["@value"]', | ||
false | ||
), | ||
description: get( | ||
supportedProperties, | ||
'["http://www.w3.org/ns/hydra/core#description"][0]["@value"]', | ||
"" | ||
), | ||
maxCardinality: get( | ||
supportedProperty, | ||
'["http://www.w3.org/2002/07/owl#maxCardinality"][0]["@value"]', | ||
null | ||
), | ||
deprecated: get( | ||
supportedProperties, | ||
'["http://www.w3.org/2002/07/owl#deprecated"][0]["@value"]', | ||
false | ||
) | ||
} | ||
); | ||
fields.push(field); | ||
resourceFields.push(field); | ||
if ( | ||
get( | ||
supportedProperties, | ||
'["http://www.w3.org/ns/hydra/core#required"][0]["@value"]', | ||
false | ||
), | ||
description: get( | ||
'["http://www.w3.org/ns/hydra/core#readable"][0]["@value"]' | ||
) | ||
) { | ||
readableFields.push(field); | ||
} | ||
if ( | ||
get( | ||
supportedProperties, | ||
'["http://www.w3.org/ns/hydra/core#description"][0]["@value"]', | ||
"" | ||
), | ||
maxCardinality: get( | ||
supportedProperty, | ||
'["http://www.w3.org/2002/07/owl#maxCardinality"][0]["@value"]', | ||
null | ||
), | ||
deprecated: get( | ||
supportedProperties, | ||
'["http://www.w3.org/2002/07/owl#deprecated"][0]["@value"]', | ||
false | ||
'["http://www.w3.org/ns/hydra/core#writable"][0]["@value"]' | ||
) | ||
) { | ||
writableFields.push(field); | ||
} | ||
); | ||
} | ||
fields.push(field); | ||
resourceFields.push(field); | ||
// parse entrypoint's operations (a.k.a. collection operations) | ||
if (property["http://www.w3.org/ns/hydra/core#supportedOperation"]) { | ||
for (const entrypointOperation of property[ | ||
"http://www.w3.org/ns/hydra/core#supportedOperation" | ||
]) { | ||
if ( | ||
!entrypointOperation["http://www.w3.org/ns/hydra/core#returns"] | ||
) { | ||
continue; | ||
} | ||
if ( | ||
get( | ||
supportedProperties, | ||
'["http://www.w3.org/ns/hydra/core#readable"][0]["@value"]' | ||
) | ||
) { | ||
readableFields.push(field); | ||
} | ||
const range = | ||
entrypointOperation[ | ||
"http://www.w3.org/ns/hydra/core#returns" | ||
][0]["@id"]; | ||
const operation = new Operation( | ||
entrypointOperation[ | ||
"http://www.w3.org/2000/01/rdf-schema#label" | ||
][0]["@value"], | ||
{ | ||
method: | ||
entrypointOperation[ | ||
"http://www.w3.org/ns/hydra/core#method" | ||
][0]["@value"], | ||
expects: | ||
entrypointOperation[ | ||
"http://www.w3.org/ns/hydra/core#expects" | ||
] && | ||
entrypointOperation[ | ||
"http://www.w3.org/ns/hydra/core#expects" | ||
][0]["@id"], | ||
returns: range, | ||
types: entrypointOperation["@type"], | ||
deprecated: get( | ||
entrypointOperation, | ||
'["http://www.w3.org/2002/07/owl#deprecated"][0]["@value"]', | ||
false | ||
) | ||
} | ||
); | ||
if ( | ||
get( | ||
supportedProperties, | ||
'["http://www.w3.org/ns/hydra/core#writable"][0]["@value"]' | ||
) | ||
) { | ||
writableFields.push(field); | ||
resourceOperations.push(operation); | ||
operations.push(operation); | ||
} | ||
} | ||
} | ||
// parse entrypoint's operations (a.k.a. collection operations) | ||
if (property["http://www.w3.org/ns/hydra/core#supportedOperation"]) { | ||
for (const entrypointOperation of property[ | ||
// parse resource operations (a.k.a. item operations) | ||
for (const supportedOperation of relatedClass[ | ||
"http://www.w3.org/ns/hydra/core#supportedOperation" | ||
]) { | ||
if ( | ||
!entrypointOperation["http://www.w3.org/ns/hydra/core#returns"] | ||
!supportedOperation["http://www.w3.org/ns/hydra/core#returns"] | ||
) { | ||
@@ -323,7 +370,7 @@ continue; | ||
const range = | ||
entrypointOperation["http://www.w3.org/ns/hydra/core#returns"][0][ | ||
supportedOperation["http://www.w3.org/ns/hydra/core#returns"][0][ | ||
"@id" | ||
]; | ||
const operation = new Operation( | ||
entrypointOperation[ | ||
supportedOperation[ | ||
"http://www.w3.org/2000/01/rdf-schema#label" | ||
@@ -333,16 +380,16 @@ ][0]["@value"], | ||
method: | ||
entrypointOperation[ | ||
supportedOperation[ | ||
"http://www.w3.org/ns/hydra/core#method" | ||
][0]["@value"], | ||
expects: | ||
entrypointOperation[ | ||
supportedOperation[ | ||
"http://www.w3.org/ns/hydra/core#expects" | ||
] && | ||
entrypointOperation[ | ||
supportedOperation[ | ||
"http://www.w3.org/ns/hydra/core#expects" | ||
][0]["@id"], | ||
returns: range, | ||
types: entrypointOperation["@type"], | ||
types: supportedOperation["@type"], | ||
deprecated: get( | ||
entrypointOperation, | ||
supportedOperation, | ||
'["http://www.w3.org/2002/07/owl#deprecated"][0]["@value"]', | ||
@@ -357,91 +404,56 @@ false | ||
} | ||
} | ||
// parse resource operations (a.k.a. item operations) | ||
for (const supportedOperation of relatedClass[ | ||
"http://www.w3.org/ns/hydra/core#supportedOperation" | ||
]) { | ||
if (!supportedOperation["http://www.w3.org/ns/hydra/core#returns"]) { | ||
continue; | ||
const url = get(entrypoint, `[0]["${property["@id"]}"][0]["@id"]`); | ||
if (!url) { | ||
throw new Error(`Unable to find the URL for "${property["@id"]}".`); | ||
} | ||
const range = | ||
supportedOperation["http://www.w3.org/ns/hydra/core#returns"][0][ | ||
"@id" | ||
]; | ||
const operation = new Operation( | ||
supportedOperation["http://www.w3.org/2000/01/rdf-schema#label"][0][ | ||
"@value" | ||
], | ||
{ | ||
method: | ||
supportedOperation["http://www.w3.org/ns/hydra/core#method"][0][ | ||
"@value" | ||
], | ||
expects: | ||
supportedOperation["http://www.w3.org/ns/hydra/core#expects"] && | ||
supportedOperation[ | ||
"http://www.w3.org/ns/hydra/core#expects" | ||
][0]["@id"], | ||
returns: range, | ||
types: supportedOperation["@type"], | ||
resources.push( | ||
new Resource(guessNameFromUrl(url, entrypointUrl), url, { | ||
id: relatedClass["@id"], | ||
title: get( | ||
relatedClass, | ||
'["http://www.w3.org/ns/hydra/core#title"][0]["@value"]', | ||
"" | ||
), | ||
fields: resourceFields, | ||
readableFields, | ||
writableFields, | ||
operations: resourceOperations, | ||
deprecated: get( | ||
supportedOperation, | ||
relatedClass, | ||
'["http://www.w3.org/2002/07/owl#deprecated"][0]["@value"]', | ||
false | ||
) | ||
} | ||
), | ||
parameters: [] | ||
}) | ||
); | ||
resourceOperations.push(operation); | ||
operations.push(operation); | ||
} | ||
const url = get(entrypoint, `[0]["${property["@id"]}"][0]["@id"]`); | ||
if (!url) { | ||
throw new Error(`Unable to find the URL for "${property["@id"]}".`); | ||
// Resolve references | ||
for (const field of fields) { | ||
if (null !== field.reference) { | ||
field.reference = | ||
resources.find(resource => resource.id === field.reference) || | ||
null; | ||
} | ||
} | ||
resources.push( | ||
new Resource(guessNameFromUrl(url, entrypointUrl), url, { | ||
id: relatedClass["@id"], | ||
title: get( | ||
relatedClass, | ||
'["http://www.w3.org/ns/hydra/core#title"][0]["@value"]', | ||
"" | ||
), | ||
fields: resourceFields, | ||
readableFields, | ||
writableFields, | ||
operations: resourceOperations, | ||
deprecated: get( | ||
relatedClass, | ||
'["http://www.w3.org/2002/07/owl#deprecated"][0]["@value"]', | ||
false | ||
) | ||
}) | ||
); | ||
} | ||
// Resolve references | ||
for (const field of fields) { | ||
if (null !== field.reference) { | ||
field.reference = | ||
resources.find(resource => resource.id === field.reference) || null; | ||
} | ||
} | ||
return Promise.resolve({ | ||
api: new Api(entrypointUrl, { title, resources }), | ||
response, | ||
status: response.status | ||
}); | ||
}, | ||
({ response }) => | ||
Promise.reject({ | ||
api: new Api(entrypointUrl, { resources: [] }), | ||
response, | ||
status: get(response, "status") | ||
}) | ||
); | ||
return Promise.resolve({ | ||
api: new Api(entrypointUrl, { title, resources }), | ||
response, | ||
status: response.status | ||
}); | ||
}, | ||
data => | ||
Promise.reject({ | ||
api: new Api(entrypointUrl, { resources: [] }), | ||
error: data, | ||
response: data.response, | ||
status: get(data.response, "status") | ||
}) | ||
) | ||
.then(({ api, response, status }) => | ||
addParameters(api).then(api => ({ api, response, status })) | ||
); | ||
} |
@@ -555,2 +555,21 @@ import parseHydraDocumentation from "./parseHydraDocumentation"; | ||
const resourceCollection = `{ | ||
"hydra:search": { | ||
"hydra:mapping": [] | ||
} | ||
}`; | ||
const resourceCollectionWithParameters = `{ | ||
"hydra:search": { | ||
"hydra:mapping": [ | ||
{ | ||
"property": "isbn", | ||
"variable": "isbn", | ||
"range": "http://www.w3.org/2001/XMLSchema#string", | ||
"required": false | ||
} | ||
] | ||
} | ||
}`; | ||
const book = { | ||
@@ -747,3 +766,11 @@ name: "books", | ||
], | ||
deprecated: false | ||
deprecated: false, | ||
parameters: [ | ||
{ | ||
variable: "isbn", | ||
range: "http://www.w3.org/2001/XMLSchema#string", | ||
required: false, | ||
description: "" | ||
} | ||
] | ||
}; | ||
@@ -891,3 +918,4 @@ | ||
], | ||
deprecated: false | ||
deprecated: false, | ||
parameters: [] | ||
}; | ||
@@ -1018,3 +1046,4 @@ | ||
], | ||
deprecated: false | ||
deprecated: false, | ||
parameters: [] | ||
}; | ||
@@ -1079,9 +1108,12 @@ | ||
], | ||
deprecated: true | ||
deprecated: true, | ||
parameters: [] | ||
}; | ||
const resources = [book, review, customResource, deprecatedResource]; | ||
const expectedApi = { | ||
entrypoint: "http://localhost", | ||
title: "API Platform's demo", | ||
resources: [book, review, customResource, deprecatedResource] | ||
resources: resources | ||
}; | ||
@@ -1099,8 +1131,15 @@ | ||
test("parse a Hydra documentation", () => { | ||
fetch.mockResponses([entrypoint, init], [docs, init]); | ||
test("parse a Hydra documentation", async () => { | ||
fetch.mockResponses( | ||
[entrypoint, init], | ||
[docs, init], | ||
[resourceCollectionWithParameters, init], | ||
[resourceCollection, init], | ||
[resourceCollection, init], | ||
[resourceCollection, init] | ||
); | ||
const options = { headers: new Headers({ CustomHeader: "customValue" }) }; | ||
return parseHydraDocumentation("http://localhost", options).then(data => { | ||
await parseHydraDocumentation("http://localhost", options).then(data => { | ||
expect(JSON.stringify(data.api, null, 2)).toBe( | ||
@@ -1112,4 +1151,5 @@ JSON.stringify(expectedApi, null, 2) | ||
expect(fetch).toHaveBeenCalledTimes(2); | ||
expect(fetch).toHaveBeenLastCalledWith( | ||
expect(fetch).toHaveBeenCalledTimes(2 + resources.length); | ||
expect(fetch).toHaveBeenNthCalledWith( | ||
2, | ||
"http://localhost/docs.jsonld", | ||
@@ -1121,6 +1161,13 @@ options | ||
test("parse a Hydra documentation (http://localhost/)", () => { | ||
fetch.mockResponses([entrypoint, init], [docs, init]); | ||
test("parse a Hydra documentation (http://localhost/)", async () => { | ||
fetch.mockResponses( | ||
[entrypoint, init], | ||
[docs, init], | ||
[resourceCollectionWithParameters, init], | ||
[resourceCollection, init], | ||
[resourceCollection, init], | ||
[resourceCollection, init] | ||
); | ||
return parseHydraDocumentation("http://localhost/").then(data => { | ||
await parseHydraDocumentation("http://localhost/").then(data => { | ||
expect(JSON.stringify(data.api, null, 2)).toBe( | ||
@@ -1127,0 +1174,0 @@ JSON.stringify(expectedApi, null, 2) |
@@ -5,2 +5,3 @@ // @flow | ||
import Operation from "./Operation"; | ||
import Parameter from "./Parameter"; | ||
@@ -13,2 +14,3 @@ type ResourceOptions = { | ||
writableFields?: Field[], | ||
parameters?: Parameter[], | ||
operations?: Operation[] | ||
@@ -15,0 +17,0 @@ }; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
138704
34
4196
57
19