Files
shields/services/transform-example.js
Paul Melnikow 2045489019 Service definition export format (#2397)
Three main goals:

1. In the front end:
      a. Show form fields and automatically assemble badge URLs (#701)
      c. Group together examples for the same service
      b. Show deprecated services
2. Make it easy to changing the schema of `examples`, thanks to 100% validation. One challenge with frameworks is that when there are typos things fail silently which is pretty unfriendly to developers. The validation should really help with that. (This caught one bug in AUR, though I fixed it in #2405 which landed first.)
3. Produce a service definition export for external tool builders. (#776)
4. Build toward harmony between the front-end data structure and the `examples` key in the service classes. I aliased `staticPreview` to `staticExample` which starts this process.

The old format:

- Lacked a consistent machine-readable representation of the fields.
- Flattened multiple examples for the same service were flattened.
- Excluded deprecated services.

The new format improves a few things, too:

- It cleans up the naming. Since this file evolved over time, the names were a bit muddled (i.e. what was an example vs a preview).
- It duplicated information (like `.svg`). (I can imagine dropping the `.svg` from our badge URLs someday, which would make the URLs easier to read and maintain.)
- For a human reading the YAML file, providing the static example as a deconstructed object is more readable.

Here are a couple snippets:

```yml
  - category: build
    name: AppVeyorCi
    isDeprecated: false
    route:
      format: '([^/]+/[^/]+)(?:/(.+))?'
      queryParams: []
    examples:
      - title: AppVeyor
        example: {path: /appveyor/ci/gruntjs/grunt, queryParams: {}}
        preview: {label: build, message: passing, color: brightgreen}
        keywords: []
      - title: AppVeyor branch
        example: {path: /appveyor/ci/gruntjs/grunt/master, queryParams: {}}
        preview: {label: build, message: passing, color: brightgreen}
        keywords: []
  - category: downloads
    name: AmoDownloads
    isDeprecated: false
    examples:
      - title: Mozilla Add-on
        example: {path: /amo/d/dustman, queryParams: {}}
        preview: {path: /amo/d/dustman, queryParams: {}}
        keywords: [amo, firefox]
```
2018-12-02 11:21:30 -05:00

142 lines
3.4 KiB
JavaScript

'use strict'
const Joi = require('joi')
const optionalObjectOfKeyValues = Joi.object().pattern(
/./,
Joi.string().allow(null)
)
const optionalServiceData = Joi.object({
label: Joi.string(),
message: Joi.alternatives()
.try(
Joi.string()
.allow('')
.required(),
Joi.number()
)
.required(),
color: Joi.string(),
})
const schema = Joi.object({
// This should be:
// title: Joi.string().required(),
title: Joi.string(),
namedParams: optionalObjectOfKeyValues,
queryParams: optionalObjectOfKeyValues.default({}),
pattern: Joi.string(),
staticPreview: optionalServiceData,
previewUrl: Joi.string(),
exampleUrl: Joi.string(),
keywords: Joi.array()
.items(Joi.string())
.default([]),
documentation: Joi.string(), // Valid HTML.
})
.rename('query', 'queryParams', { ignoreUndefined: true })
.rename('staticExample', 'staticPreview', { ignoreUndefined: true })
.rename('urlPattern', 'pattern', { ignoreUndefined: true })
.required()
function validateExample(example, index, ServiceClass) {
const result = Joi.attempt(
example,
schema,
`Example for ${ServiceClass.name} at index ${index}`
)
const { namedParams, pattern, staticPreview, previewUrl, exampleUrl } = result
if (staticPreview) {
if (!pattern && !ServiceClass.route.pattern) {
throw new Error(
`Static preview for ${
ServiceClass.name
} at index ${index} does not declare a pattern`
)
}
if (namedParams && exampleUrl) {
throw new Error(
`Static preview for ${
ServiceClass.name
} at index ${index} declares both namedParams and exampleUrl`
)
} else if (!namedParams && !exampleUrl) {
throw new Error(
`Static preview for ${
ServiceClass.name
} at index ${index} does not declare namedParams nor exampleUrl`
)
}
if (previewUrl) {
throw new Error(
`Static preview for ${
ServiceClass.name
} at index ${index} also declares a dynamic previewUrl, which is not allowed`
)
}
} else if (!previewUrl) {
throw Error(
`Example for ${
ServiceClass.name
} at index ${index} is missing required previewUrl or staticPreview`
)
}
return result
}
function transformExample(inExample, index, ServiceClass) {
const {
// We should get rid of this transform, since the class name is never what
// we want to see.
title = ServiceClass.name,
namedParams,
queryParams,
pattern,
staticPreview,
previewUrl,
exampleUrl,
keywords,
documentation,
} = validateExample(inExample, index, ServiceClass)
let example
if (namedParams) {
example = {
pattern: ServiceClass._makeFullUrl(pattern),
namedParams,
queryParams,
}
} else {
example = {
path: ServiceClass._makeFullUrl(exampleUrl || previewUrl),
queryParams,
}
}
let preview
if (staticPreview) {
const badgeData = ServiceClass._makeBadgeData({}, staticPreview)
preview = {
label: badgeData.text[0],
message: `${badgeData.text[1]}`,
color: badgeData.colorscheme || badgeData.colorB,
}
} else {
preview = {
path: ServiceClass._makeFullUrl(previewUrl),
queryParams,
}
}
return { title, example, preview, keywords, documentation }
}
module.exports = {
validateExample,
transformExample,
}