Skip to content

Latest commit

 

History

History
368 lines (303 loc) · 19.6 KB

wire.md

File metadata and controls

368 lines (303 loc) · 19.6 KB

Conjure Wire Specification

This document defines how clients and servers should behave based on endpoints and types defined in a Conjure IR file.

1. Conventions

For convenience, we define a de-alias function which recursively collapses the Conjure Alias type and is an identity function otherwise:

de-alias(Alias of T) -> de-alias(T)
de-alias(T) -> T

2. HTTP requests

This section assumes familiarity with HTTP concepts as defined in RFC2616 Hypertext Transfer Protocol -- HTTP/1.1.

2.1. Path parameters

Path parameters are interpolated into the path string, where values are serialized using the PLAIN format and must also be URL encoded to ensure reserved characters are transmitted unambiguously.

For example, the following Conjure endpoint contains several path parameters of different types:

demoEndpoint:
  http: GET /demo/{file}/rev/{revision}
  args:
    file: string
    revision: integer

In this example, the file argument with value var/conf/install.yml is percent encoded:

/demo/var%2Fconf%2Finstall.yml/rev/53

2.2. Query parameters

Parameters of type query must be translated into a query string, with the Conjure paramId used as the query key. If a value of de-aliased type optional<T> is not present, then the key must be omitted from the query string. Otherwise, the inner value must be serialized using the PLAIN format and any reserved characters URL encoded.

For example, the following Conjure endpoint contains some query parameters:

demoEndpoint:
  http: GET /recipes
  args:
    filter:
      param-type: query
      type: optional<string>
    limit:
      param-type: query
      type: optional<integer>
    categories:
      param-id: category
      param-type: query
      type: list<string>

These examples illustrate how an optional<T> value should be omitted if the value is not present

/recipes?filter=Hello%20World&limit=10
/recipes?filter=Hello%20World
/recipes

For query parameters of type list<T> or set<T>, each value should result in one key=value pair, separated by &. Note that the order of values must be preserved for list<T>, but is semantically unimportant for set<T>. E.g.:

/recipes?category=foo&category=bar&category=baz

2.3. Body parameter

The endpoint body argument must be serialized using the JSON format, unless:

  • the de-aliased argument is type binary: the clients must write the raw binary bytes directly to the request body
  • the de-aliased argument is type optional<T> and the value is not present: it is recommended to send an empty request body, although clients may alternatively send the JSON value null. It is recommended to add a Content-Length header for compatibility with HTTP/1.0 servers.

For example, the following Conjure endpoint defines a request body:

demoEndpoint:
  http: POST /names
  args:
    newName:
      type: optional<string>
      param-type: body

In this case, if newName is not present, then the JSON format allows clients to send a HTTP body containing null or send an empty body. If newName is present, then the body will include JSON quotes, e.g. "Joe blogs".

2.4. Headers

Conjure header parameters must be serialized in the PLAIN format and transferred as HTTP Headers. Header names are case insensitive. Parameters of Conjure type optional<T> must be omitted entirely if the value is not present, otherwise just serialized using the PLAIN Format.

2.4.1. Content-Type Header

A Content-Type header must be added if the endpoint defines a body argument.

  • If the de-aliased body type is binary, the Content-Type application/octet-stream must be used.
  • Otherwise, clients must use application/json.

Note that the default encoding for application/json content type is UTF-8.

2.4.2. Accept header

Clients must send an Accept: application/json header for all requests unless the endpoint returns binary, in which case the client must send Accept: application/octet-stream.

2.4.3. User-agent

Where possible, requests must include a User-Agent header defined below using ABNF notation and regexes:

User-Agent        = commented-product *( WHITESPACE commented-product )
commented-product = product | product WHITESPACE paren-comments
product           = name "/" version
paren-comments    = "(" comments ")"
comments          = comment-text *( delim comment-text )
delim             = "," | ";"

comment-text      = [^,;()]+
name              = [a-zA-Z][a-zA-Z0-9\-]*
version           = [0-9]+(\.[0-9]+)*(-rc[0-9]+)?(-[0-9]+-g[a-f0-9]+)?

For example, the following are valid user agents:

foo/1.0.0
my-service/1.0.0-rc3-18-g773fc1b conjure-java-runtime/4.6.0 okhttp3/3.11.0
bar/0.0.0 (nodeId:myNode)
Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36

Note, this is more restrictive than the User-Agent definition in RFC 7231. Requests from some browsers may not comply with these requirements as it is impossible to override browser User-Agent headers.

2.4.4. Header Authorization

For endpoints with auth of type header, clients must send a header with name Authorization and case-sensitive value Bearer {{string}} where {{string}} is a user-provided string.

2.4.5. Cookie Authorization

For endpoints with auth of type cookie, clients must send a cookie header with value {{cookieName}}={{value}}, where {{cookieName}} comes from the IR and {{value}} is a user-provided value.

2.4.6. Additional headers

Clients may inject additional headers (e.g. for Zipkin tracing, or Fetch-User-Agent), as long as these do not clash with any headers already specified in the endpoint definition.

3. HTTP responses

3.1. Status codes

Conjure servers must respond to successful requests with HTTP status 200 OK unless:

  • the de-aliased return type is optional<T> and the value is not present: servers must send 204 No Content.
  • the de-aliased return type is a map, list or set: it is recommended to send 204 but servers may send 200 if the HTTP body is [] or {}.

Using 204 in this way ensures that clients calling a Conjure endpoint with optional<binary> return type can differentiate between a non-present optional (204) and a present binary value containing zero bytes (200).

Further non-successful status codes are defined in the Conjure errors section below.

3.2. Response body

Conjure servers must serialize return values using the JSON format defined below, unless:

  • the de-aliased return type is optional<T> and the value is not present: servers must return an empty HTTP body
  • the de-aliased return type is optional<binary> and the value is present: servers must write the raw bytes as an octet-stream
  • the de-aliased return type is binary: servers must write the raw bytes as an octet-stream

3.3. Content-Type header

Conjure servers must send a Content-Type header according to the endpoint's return type:

  • if the de-aliased return type is binary, servers must send Content-Type: application/octet-stream,
  • otherwise, servers must send Content-Type: application/json.

3.4. Conjure errors

In order to send a Conjure error, servers must serialize the error using the JSON format. In addition, servers must send a http status code corresponding to the error's code.

Conjure Error code HTTP Status code
PERMISSION_DENIED 403
INVALID_ARGUMENT 400
NOT_FOUND 404
CONFLICT 409
REQUEST_ENTITY_TOO_LARGE 413
FAILED_PRECONDITION 500
INTERNAL 500
TIMEOUT 500
CUSTOM_CLIENT 400
CUSTOM_SERVER 500

4. Behaviour

4.1. Forward compatible clients

Clients must tolerate extra headers, unknown fields in JSON objects and unknown variants of Conjure enums and unions. This ensures that old clients will continue to work, even if a newer version of a server includes extra fields in a JSON response.

4.2. Servers reject unknown fields

Servers must request reject all unexpected JSON fields. This helps developers notice bugs and mistakes quickly, instead of allowing silent failures.

4.3. Servers tolerate extra headers

Servers must tolerate extra headers not defined in the endpoint definition. This is important because proxies frequently modify requests to include additional headers, e.g. X-Forwarded-For.

4.4. Round-trip of unknown variants

Clients should be able to round trip unknown variants of enums and unions.

4.5. CORS and HTTP preflight requests

In order to be compatible with browser preflight requests, servers must support the HTTP OPTIONS method.

4.6. HTTP/2

The Conjure wire specification is compatible with HTTP/2, but it is not required.

5. JSON format

This format describes how all Conjure types are serialized into and deserialized from JSON (RFC 7159).

5.1. Built-in types

Conjure Type JSON Type Comments
bearertoken String In accordance with RFC 6750.
binary String Represented as a Base64 encoded string in accordance with RFC 4648.
boolean Boolean
datetime String In accordance with ISO 8601.
double Number or "NaN" or "Infinity" or "-Infinity" As defined by IEEE 754 standard.
integer Number Signed 32 bits, value ranging from -231 to 231 - 1.
rid String In accordance with the Resource Identifier definition.
safelong Number Integer with value ranging from -253 + 1 to 253 - 1.
string String UTF-8 string
uuid String In accordance with RFC 4122.
any N/A May be any of the above types or an object with any fields.

5.2. Container types

Conjure Type JSON Type Comments
optional<T> JSON(T) or null If present, must be serialized as JSON(e). If the value appears inside a JSON Object, then the corresponding key should be omitted. Alternatively, the field may be set to null. Inside JSON Array, a non-present Conjure optional value must be serialized as JSON null.
list<T> Array Each element, e, of the list is serialized using JSON(e). Order must be maintained.
set<T> Array Each element, e, of the set is serialized using JSON(e). Order is insignificant but it is recommended to preserve order where possible.
map<K, V> Object A key k is serialized as a string with contents PLAIN(k). Values are serialized using JSON(v). For any (key,value) pair where the value is of de-aliased type optional<?>, the key should be omitted from the JSON Object if the value is absent, however, the key may remain if the value is set to null.

5.3. Named types

Conjure Type JSON Type Comments
Object Object Keys are obtained from the Conjure object's fields and values using JSON(v). For any (key,value) pair where the value is of optional<?> type, the key must be omitted from the JSON Object if the value is absent.
Enum String String representation of the enum value
Union Object (See union JSON format below)
Alias(x) JSON(x) Aliases are serialized exactly the same way as their corresponding de-aliased Conjure types.

5.4. Union JSON format

Conjure Union types are serialized as JSON objects with exactly two keys:

  1. type key - this determines the variant of the union, e.g. foo
  2. {{variant}} key - this key must match the variant determined above, and the value is JSON(v). Example union type definition:
types:
  definitions:
    objects:

      MyUnion:
        union:
          foo: boolean
          bar: list<string>

Example union type in JSON representation:

// In this example the variant is `foo` and the inner type is a Conjure `boolean`.
{
  "type": "foo",
  "foo": true
}
// In this example, the variant is `bar` and the inner type is a Conjure `list<string>`
{
  "type": "bar",
  "bar": ["Hello", "world"]
}

5.5. Conjure Errors

Conjure Errors are serialized as JSON objects with the following keys:

  1. errorCode - the JSON string representation of one of the supported Conjure error codes.
  2. errorName - a JSON string identifying the error, e.g. Recipe:RecipeNotFound.
  3. errorInstanceId - a JSON string containing the unique identifier, uuid type, for this error instance.
  4. parameters - a JSON map providing additional information regarding the nature of the error.

Example error type definition:

types:
  definitions:
    errors:
      RecipeNotFound:
        namespace: Recipe
        code: NOT_FOUND
        safe-args:
          name: RecipeName

Example error type in JSON presentation:

{
    "errorCode": "NOT_FOUND",
    "errorName": "Recipe:RecipeNotFound",
    "errorInstanceId": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
    "parameters": {
        "name": "roasted broccoli with garlic"
    }
}

5.6. Deserialization

5.6.1. Coercing JSON null / absent to Conjure types

If a JSON key is absent or the value is null, two rules apply:

  • Conjure optional, list, set, map types must be initialized to their empty variants,
  • Attempting to coerce null/absent to any other Conjure type must cause an error, i.e. missing JSON keys must cause an error.

Note: this rule means that the Conjure type optional<optional<T>> would be ambiguously deserialized from null: it could be Optional.empty() or Optional.of(Optional.empty()). To avoid this ambiguity, Conjure ensures definitions do not contain this type.

5.6.2. No automatic casting

Unexpected JSON types should not be automatically coerced to a different expected type. For example, if a Conjure definition specifies a field is boolean, the JSON strings "true" and "false" should not be accepted.

6. PLAIN format

The PLAIN format describes an unquoted JSON representation of a subset of de-aliased conjure types. The types listed below have a PLAIN format representation, while those omitted do not.

Conjure Type PLAIN Representation Comments
bearertoken unquoted String In accordance with RFC 6750.
binary unquoted String Represented as a Base64 encoded string in accordance with RFC 4648.
boolean true or false
datetime unquoted String In accordance with ISO 8601.
double Number or NaN or Infinity or -Infinity As defined by IEEE 754 standard.
integer Number Signed 32 bits, value ranging from -231 to 231 - 1.
rid unquoted String In accordance with the Resource Identifier definition.
safelong Number Integer with value ranging from -253 + 1 to 253 - 1.
string unquoted String UTF-8 string
uuid unquoted String In accordance with RFC 4122.
Enum unquoted variant name UTF-8 string