Added specification documents into public domain

main
Tom Peltonen 2023-04-24 23:06:33 +10:00
parent 67ebcad410
commit 3bb4d5f8e7
2 changed files with 860 additions and 0 deletions

View File

@ -0,0 +1,844 @@
{
"openapi": "3.0.2",
"info": {
"title": "Qaskx OpenAPI template",
"version": "0.1.0",
"description": "# {{serviceName}} Service Domain\n\nThis document covers the specification of the {{serviceName}} API to be used by consumers.\n\n\n## Purpose\n\nProvide a **??TODO??** capability \n\n\n## Service Domains\n\nThe Service Domain covers the following:\n\n* item 1 **??TODO??**\n* item 2\n* item 3\n\n## Navigation\n\nThe service definition provides hypertext media links. The links attribute is named **links** and is consistent across all the paths in name and general structure.\nThe **rel** attribute provides information on the relationship of the link (href) to the resource it is attached to.\n\nProviding a consistent **links** and **rel** values allows for UX lists and navigation by drill down to be built with very little predetermined knowledge about the service paths and their names.\n\n## Security\n\nSecurity requirements for the service is common across all paths and is\n**??TODO??**\n\n## Non functional requirements\n\nThe non functional requirements (NFR) are defined at implementation level. As an implementation\ncan be productional and non production, different expectations or service level agreements (SLA)\napply.\n\nThe NFR include: **??TODO??**\n\n* Return point objective (RPO): 5 minutes\n* Return time objective (RTO): 30 minutes\n* Mean response time: 300 milliseconds\n* Timeout: 5 seconds\n* Uptime: 99.99% excluding agreed maintenance outages\n* Security: All service endpoint require authentication except the base health service\n* Encryption: All communications is to be encrypted with TLS 1.2 or later\n* Logging: Basic request and response details are recorded as are failures\n\nMore details on NFRs can be viewed at **??TODO??**\n\n## Health\n\nTwo health service end points is provided. The simpler \"/health\" service endpoint only provides a simple OK status \nand is open to public. The \"/health/details\" service end point requires authentication as it provides\nconfidential information.\n\nIf the response is a HTTP status code 200 then some or all dependeant services are operational \nfor searching. The health check will provide information on what dependant services are not available. If the main service \nto handle the incoming request is unavailable then a non 200 HTTP status code is returned\n\nA non 200 HTTP status code return value indicates an unhealthy service. The service will not return any results.\n\n\n## Scenarios\n\n**??TODO??**\n\n### Scenario 1\n\n\n### Scenario 2\n\n\n## References\n\nFurther service domain information is available under each service end point and at the following locations:\n\n* https://\n* **??TODO??**\n \n\n",
"contact": {
"name": "Qaskx",
"url": "https://",
"email": "qaskx+default@merebox.com"
}
},
"paths": {
"/health": {
"summary": "Health service",
"description": "This service end point is to discover the health of the\nservice. The information provides an overall health\nand also health of individual upstream dependencies.\n\n# Standard\n\nThe health check service is modelled based on the proposal\nat https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check\n\n# Security\n\nThe service is secured to stop abuse.",
"get": {
"tags": [
"Development"
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/healtbase-model"
},
"examples": {
"health01": {
"value": {
"status": "OK",
"message": "some text"
}
}
}
}
},
"description": "The service is healthy"
},
"429": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "Too many requests for the domain have been\ndetected."
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "Internal error with service, with minimal\ninformation available"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/health-model"
}
}
},
"description": "The service is unavailable"
}
},
"summary": "Retrieve the health of the search service"
}
},
"/health/details": {
"summary": "Detail health service",
"description": "This service end point is to discover the health of the\nservice in detail. The information provides an overall health\nand also health of individual upstream dependencies.\n\n# Standard\n\nThe health check service is modelled based on the proposal\nat https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check\n\n# Security\n\nThe service is secured to stop abuse and disclosure of confidential information.",
"get": {
"tags": [
"Development"
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/health-model"
},
"examples": {
"healthdetails01": {
"value": {
"status": "PASS",
"version": "some text",
"notes": [
"some text",
"some text"
],
"output": "some text",
"description": "some text",
"checks": {
},
"release_id": "some text",
"service_id": "some text",
"links": [
{
"href": "some text",
"rel": "some text",
"operation": "PATCH",
"caption": "some text",
"media_type": "some text"
},
{
"href": "some text",
"rel": "some text",
"operation": "DELETE",
"caption": "some text",
"media_type": "some text"
}
]
}
}
}
}
},
"description": "The service is healthy"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "The requestor is not authenticated"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "While the requestor has been authenticated\nthey are forbidden to access the service."
},
"429": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "Too many requests for the domain have been\ndetected."
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "Internal error with service, with minimal\ninformation available"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/health-model"
}
}
},
"description": "The service is unavailable"
}
},
"security": [
{
"BasicAuth": [
],
"ApiKey": [
],
"BearerAuth": [
]
}
],
"summary": "Retrieve the health of the search service"
}
},
"/reference/types/": {
"summary": "Listing valid types supported by this service",
"description": "This is a sample list of object types\nsupported by the service.\n\nIt can be deleted if not required or used as \na template for other reference data or\nrenamed to support reference data.",
"get": {
"tags": [
"Proposed"
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/referencetype-list"
}
}
},
"description": "Returns a list of valid domains.\n\nAs the list is small (under 100 entries)\nand the each definition is small there\nis no pagination."
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "The request is unauthorized."
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "Category resource not found"
},
"429": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "Too many requests for the domain have been\ndetected."
},
"500": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/error-model"
}
}
},
"description": "Internal error with service, with minimal\ninformation available"
},
"503": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/health-model"
}
}
},
"description": "The service is unavailable"
}
},
"security": [
{
"BearerAuth": [
],
"ApiKey": [
],
"BasicAuth": [
]
}
],
"summary": "Retrieve list of valid types"
}
}
},
"components": {
"schemas": {
"healthcheck-model": {
"title": "healthcheck-model",
"description": "",
"type": "object",
"properties": {
"status": {
"type": "string"
},
"output": {
"type": "string"
},
"affected_end_points": {
"type": "array",
"items": {
"type": "string"
}
},
"component_id": {
"type": "string"
},
"component_type": {
"type": "string"
},
"observed_unit": {
"type": "string"
},
"observed_value": {
"format": "float",
"type": "number"
},
"as_at": {
"format": "date-time",
"type": "string"
}
},
"example": {
"status": "some text",
"output": "some text",
"affected_end_points": [
"some text",
"some text"
],
"component_id": "some text",
"component_type": "some text",
"observed_unit": "some text",
"observed_value": 81.39,
"as_at": "2018-02-10T09:30Z"
}
},
"error-model": {
"title": "error-response",
"description": "A common error payload returned \nwhen the response code is not 2xx",
"type": "object",
"properties": {
"status": {
"description": "Status identifier that can be used to identify the\ncause of the error.\n\nIt s not the HTTP status code (eg 4xx ot 5xx)",
"type": "string"
},
"message": {
"description": "Error description, that should be less technical \nand more user orientated where possible",
"type": "string"
},
"resolution": {
"description": "Information on how the error or issue may\nbe resolved.",
"type": "string"
},
"technical": {
"description": "Technical information for the error.\n\nThis must not contain sensitive information",
"type": "string"
}
},
"example": {
"status": "some text",
"message": "some text",
"resolution": "some text",
"technical": "some text"
}
},
"links-model": {
"title": "links-model",
"description": "Hypermedia links see https://restfulapi.net/hateoas/ \n",
"required": [
"rel",
"href"
],
"type": "object",
"properties": {
"href": {
"type": "string"
},
"rel": {
"description": "The relationships for the resource as hypermedia link.\n\nThe common values are:\n\n* self : this resource. Normally used in lists,\n* first : the first resource in a list, \n* last : the last resource in a list, \n* next : the next resource or page in a list, \n* prev : the previous resource or page in a list\n* parent: the parent resource for the item\n* children: list of children of the resource\n* buy: link to purchase the asset\n* sell: link to sell the asset\n* quote: link to quote for the asset\n* icon : URL to the icon for the resource",
"type": "string"
},
"operation": {
"description": "The operation that the service resource accepts.\n\nCommonly this will be a GET or POST",
"enum": [
"GET",
"POST",
"DELETE",
"PUT",
"PATCH",
"HEAD"
],
"type": "string"
},
"caption": {
"description": "A caption that can be displayed for the link",
"type": "string"
},
"media_type": {
"description": "The type of resource media type that the link refers to.\n\nExamples are:\n\n* application/json\n* text/html",
"type": "string"
}
},
"example": {
"href": "https://server/xxx/56b3a016-213e-11ed-90db-0f0cefaf6cc5",
"rel": "self",
"operation": "GET",
"media_type": "application/json",
"caption": "Resource 1"
}
},
"meta-list": {
"title": "meta-list",
"description": "Meta data for lists. This is returned for all list services.\n\nThe page and record information can be used to \nprovide feedback to the consumer of the volume \nof search results.\n\nThis meta data for search does not include\na **page_token** value as the same intended \nfunctionality can be achieved by using\nthe search identifier for the request.",
"required": [
"page_size",
"total_pages",
"total_records",
"incomplete_total",
"current_page"
],
"type": "object",
"properties": {
"incomplete_total": {
"description": "A flag to indicate the total number of records\nis incomplete and there are more records.\n\nThe search engine has stopped further processing\nand so has not arrived at a total number for the\nreason that the number is large and to determine\na true number would require extra processing and \ndelay the results being returned.\n\nIf displaying the total records or pages, you may consider \nadding a \"+\" symbol to denote more.",
"type": "boolean"
},
"page_size": {
"description": "The page size for pagination",
"type": "integer"
},
"total_pages": {
"format": "int32",
"description": "The total number of pages in the list.\n\nThe value will be 0 if there are no records, otherwise\nif there are records in the list the value \nwill be an integer value of 1 or greater.\n\nThis value can be used in pagination",
"type": "integer"
},
"total_records": {
"format": "int32",
"description": "The total number of records in the list\nacross all pages.\n\nThe value must be 0 (zero) or a positive integer",
"minimum": 0,
"type": "integer"
},
"current_page": {
"description": "The current page number of the returned result set.\n\nThis value can then be used to navigate backwards or\nforwards in the pagination of results. Alternatively\nthe returned links array can be used to\npaginate previous or next page and so on.",
"type": "number"
}
},
"example": {
"incomplete_total": true,
"page_size": 64,
"total_pages": 43,
"total_records": 72
}
},
"health-model": {
"title": "health-model",
"description": "The response object from a \"health\" request.\n\nThe response may be full. partial, minimal or missing \nif the status is not \"200\"",
"required": [
"status",
"checks",
"links",
"notes"
],
"type": "object",
"properties": {
"status": {
"description": " Indicates whether the service status is acceptable\r\n or not\r\n \r\n indicates whether the service status is acceptable\r\n or not. API publishers SHOULD use following values for the field:\r\n\r\n* \"pass\": healthy (acceptable aliases: \"ok\" to support Node's\r\n Terminus and \"up\" for Java's SpringBoot),\r\n\r\n* \"fail\": unhealthy (acceptable aliases: \"error\" to support Node's\r\n Terminus and \"down\" for Java's SpringBoot), and\r\n\r\n* \"warn\": healthy, with some concerns.\r\n\r\n The value of the status field is case-insensitive and is tightly\r\n related with the HTTP response code returned by the health endpoint.\r\n For \"pass\" status, HTTP response code in the 2xx-3xx range MUST be\r\n used. For \"fail\" status, HTTP response code in the 4xx-5xx range\r\n MUST be used. In case of the \"warn\" status, endpoints MUST return\r\n HTTP status in the 2xx-3xx range, and additional information SHOULD\r\n be provided, utilizing optional fields of the response.\r\n\r\n A health endpoint is only meaningful in the context of the component\r\n it indicates the health of. It has no other meaning or purpose. As\r\n such, its health is a conduit to the health of the component.\r\n Clients SHOULD assume that the HTTP response code returned by the\r\n health endpoint is applicable to the entire component (e.g. a larger\r\n API or a microservice). This is compatible with the behavior that\r\n current infrastructural tooling expects: load-balancers, service\r\n discoveries and others, utilizing health-checks.",
"enum": [
"PASS",
"WARN",
"FAIL"
],
"type": "string"
},
"version": {
"description": "Public version of the service",
"type": "string"
},
"notes": {
"description": "Array of notes relevant to current state of health\n\nThe array can be empty",
"type": "array",
"items": {
"type": "string"
}
},
"output": {
"description": "Raw error output, in case of \"fail\" or \"warn\"\r\nstates. This field SHOULD be omitted for \"pass\" state.",
"type": "string"
},
"description": {
"description": "A human-friendly description of the\r\nservice.\r\n",
"type": "string"
},
"checks": {
"description": " The \"checks\" object MAY have a number of unique keys, one for each\r\n logical downstream dependency or sub-component. Since each sub-\r\n component may be backed by several nodes with varying health\r\n statuses, these keys point to arrays of objects. In case of a\r\n single-node sub-component (or if presence of nodes is not relevant),\r\n a single-element array SHOULD be used as the value, for consistency.\r\n\r\n The key identifying an element in the object SHOULD be a unique\r\n string within the details section. It MAY have two parts:\r\n \"{componentName}:{measurementName}\", in which case the meaning of the\r\n parts SHOULD be as follows:\r\n\r\n * componentName: (optional) human-readable name for the component.\r\n MUST not contain a colon, in the name, since colon is used as a\r\n separator.\r\n\r\n * measurementName: (optional) name of the measurement type (a data\r\n point type) that the status is reported for. MUST not contain a\r\n colon, in the name, since colon is used as a separator. The\r\n observation's name can be one of:\r\n\r\n - A pre-defined value from this spec. Pre-defined values\r\n include:\r\n\r\n o utilization\r\n\r\n o responseTime\r\n\r\n o connections\r\n\r\n o uptime\r\n\r\n - A common and standard term from a well-known source such as\r\n schema.org, IANA or microformats.\r\n\r\n - A URI that indicates extra semantics and processing rules that\r\n MAY be provided by a resource at the other end of the URI.\r\n URIs do not have to be dereferenceable, however. They are just\r\n a namespace, and the meaning of a namespace CAN be provided by\r\n any convenient means (e.g. publishing an RFC, Open API Spec\r\n document or a nicely printed book).\r\n\r\n On the value side of the equation, each \"component details\" object in\r\n the array SHOULD have at least one key.",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/components/schemas/healthcheck-model"
}
}
},
"release_id": {
"description": "In well-designed APIs, backwards-compatible\r\nchanges in the service should not update a version number. APIs\r\nusually change their version number as infrequently as possible, to\r\npreserve stable interface. However, implementation of an API may\r\nchange much more frequently, which leads to the importance of having\r\nseparate \"release number\" or \"releaseId\" that is different from the\r\npublic version of the API.",
"type": "string"
},
"service_id": {
"description": "A unique identifier (UUID) for the service\n\nThe identifiers are UUID which are \nrepresented as 36 characters strings\nincluding the \"-\" between 5 groups.",
"maxLength": 36,
"minLength": 36,
"type": "string"
},
"links": {
"description": "Links to additional information or actions for the resource",
"type": "array",
"items": {
"$ref": "#/components/schemas/links-model"
}
}
},
"example": {
"status": "pass",
"version": "1",
"release_id": "1.2.2",
"notes": [
""
],
"output": "",
"service_dd": "f03e522f-1f44-4062-9b55-9587f91c9c41",
"description": "health of authz service",
"checks": {
"cassandra:responseTime": [
{
"component_id": "dfd6cf2b-1b6e-4412-a0b8-f6f7797a60d2",
"component_type": "datastore",
"observed_value": 250,
"observed_unit": "ms",
"status": "pass",
"affected_end_points": [
"/users/{userId}",
"/customers/{customerId}/status",
"/shopping/{anything}"
],
"time": "2018-01-17T03:36:48Z",
"output": ""
}
],
"cassandra:connections": [
{
"component_id": "dfd6cf2b-1b6e-4412-a0b8-f6f7797a60d2",
"component_type": "datastore",
"observed_value": 75,
"status": "warn",
"time": "2018-01-17T03:36:48Z",
"output": "",
"links": {
"self": "http://api.example.com/dbnode/dfd6cf2b/health"
}
}
],
"uptime": [
{
"component_type": "system",
"observed_value": 1209600.245,
"observed_unit": "s",
"status": "pass",
"time": "2018-01-17T03:36:48Z"
}
],
"cpu:utilization": [
{
"component_id": "6fd416e0-8920-410f-9c7b-c479000f7227",
"node": 1,
"component_type": "system",
"observed_value": 85,
"observed_unit": "percent",
"status": "warn",
"time": "2018-01-17T03:36:48Z",
"output": ""
},
{
"componentId": "6fd416e0-8920-410f-9c7b-c479000f7227",
"node": 2,
"componentType": "system",
"observedValue": 85,
"observedUnit": "percent",
"status": "warn",
"time": "2018-01-17T03:36:48Z",
"output": ""
}
],
"memory:utilization": [
{
"componentId": "6fd416e0-8920-410f-9c7b-c479000f7227",
"node": 1,
"componentType": "system",
"observedValue": 8.5,
"observedUnit": "GiB",
"status": "warn",
"time": "2018-01-17T03:36:48Z",
"output": ""
},
{
"componentId": "6fd416e0-8920-410f-9c7b-c479000f7227",
"node": 2,
"componentType": "system",
"observedValue": 5500,
"observedUnit": "MiB",
"status": "pass",
"time": "2018-01-17T03:36:48Z",
"output": ""
}
]
},
"links": {
"about": "http://api.example.com/about/authz",
"http://api.x.io/rel/thresholds": "http://api.x.io/about/authz/thresholds"
}
}
},
"healtbase-model": {
"title": "healthbase-response",
"description": "A response to the **/health** service\nand not a data model for **rediops** model\nas used in the **devops.json** definition",
"required": [
"message",
"status"
],
"type": "object",
"properties": {
"status": {
"type": "string"
},
"message": {
"type": "string"
}
},
"example": {
"status": "OK",
"message": "Available"
}
},
"referencetype-list": {
"title": "searchresult-list",
"description": "A list of types.\n\nTypes are unique within the list\nand ordered in alphabetical sequence.",
"required": [
"meta",
"items",
"links"
],
"type": "object",
"properties": {
"links": {
"description": "Links to additional information or actions for the resource.\n\nThe search result links can contain:\n\n* first page link for search results\n* previous page link for search results\n* next page for link for search results\n* last page for link for search results\n \nOther relationship links can also be included.",
"type": "array",
"items": {
"$ref": "#/components/schemas/links-model"
}
},
"meta": {
"$ref": "#/components/schemas/meta-list",
"description": "Meta data on the list service.\n\nA search will return a maximum of 500 items"
},
"as_at": {
"description": "Timestamp in ISO 8061 format denoting the date and\ntime this data list was last updated/correct on \nthe source of record.\n\nCaching of the data will not change this value.\n\nThis is not the \ncurrent service fetch timestamp.",
"maxLength": 1,
"type": "string"
},
"items": {
"description": "A list of found matches to the domain\nfor the the search criteria",
"type": "array",
"items": {
"$ref": "#/components/schemas/referencetype-list-model"
}
}
},
"example": {
"links": [
{
"href": "some text",
"rel": "some text",
"operation": "PATCH",
"caption": "some text",
"media_type": "some text"
},
{
"href": "some text",
"rel": "some text",
"operation": "PUT",
"caption": "some text",
"media_type": "some text"
}
],
"meta": {
"incomplete_total": true,
"page_size": 52,
"total_pages": 31,
"total_records": 58,
"current_page": 12.85
},
"as_at": "some text",
"items": [
{
"name": "some text",
"id": "some text",
"caption": "some text",
"link": "some text"
},
{
"name": "some text",
"id": "some text",
"caption": "some text",
"link": "some text"
}
]
}
},
"referencetype-model": {
"title": "searchresult-domain-model",
"description": "Type reference data model",
"required": [
"name",
"id",
"links",
"caption"
],
"type": "object",
"properties": {
"links": {
"description": "Links to additional information or actions for the type",
"type": "array",
"items": {
"$ref": "#/components/schemas/links-model"
}
},
"name": {
"description": "Type name in human understandable context",
"maxLength": 25,
"type": "string"
},
"id": {
"description": "Type unique identifier",
"maxLength": 36,
"type": "string"
},
"description": {
"description": "A description for the type.\n\nThis can be in Markdown (MD) format\n",
"maxLength": 1000,
"type": "string"
},
"caption": {
"description": "A short caption of the type",
"maxLength": 75,
"type": "string"
}
},
"example": {
"links": [
{
"href": "some text",
"rel": "some text",
"operation": "DELETE",
"caption": "some text",
"media_type": "some text"
},
{
"href": "some text",
"rel": "some text",
"operation": "GET",
"caption": "some text",
"media_type": "some text"
}
],
"name": "some text",
"id": "some text",
"description": "some text",
"caption": "some text"
}
},
"referencetype-list-model": {
"title": "searchresult-domain-model",
"description": "Type reference data model",
"required": [
"name",
"id",
"link",
"caption"
],
"type": "object",
"properties": {
"name": {
"description": "Type name in human understandable context",
"maxLength": 25,
"type": "string"
},
"id": {
"description": "Type unique identifier",
"maxLength": 36,
"type": "string"
},
"caption": {
"description": "A short caption of the type",
"maxLength": 75,
"type": "string"
},
"link": {
"description": "Link to item resource in this API",
"maxLength": 200,
"type": "string"
}
},
"example": {
"name": "some text",
"id": "some text",
"caption": "some text",
"link": "some text"
}
}
},
"securitySchemes": {
"BasicAuth": {
"scheme": "basic",
"type": "http"
},
"ApiKey": {
"type": "apiKey",
"name": "X-ApiKey",
"in": "header"
},
"BearerAuth": {
"scheme": "bearer",
"type": "http"
}
}
},
"tags": [
{
"name": "Proposed",
"description": "API has been proposed but no detail specifications has yet been drafted"
},
{
"name": "Deprecated",
"description": "API is marked as deprecated and no longer available for new consumers.\nExisting consumers should plan transitioning out of usage."
},
{
"name": "Design",
"description": "API is currently being specified and it will change"
},
{
"name": "Development",
"description": "API has been designed but is still in development. It may change."
},
{
"name": "Published",
"description": "API has been developed and is available for consumption."
}
]
}

16
specs/qaskx.json 100644
View File

@ -0,0 +1,16 @@
{
"GitProvider": "gitea",
"GitHost": "https://tea.merebox.com",
"GitApi": "https://tea.merebox.com/api/v1",
"GitOrg": "Wardrobe",
"GitRepoUser": "Wardrobe",
"GitBranch": "main",
"Organisation": "Qaskx org",
"Name": "Qaskx name",
"ServiceOwner": "Qaskx owner",
"ServiceOwnerEmail": "Qaskx@merebox.com",
"TemplateFolder": "https://git/",
"PrimaryProgramLanguage": "go",
"GitUserEmail": "tom@LAP20"
}