This document defines how clients and servers should behave based on endpoints and types defined in a Conjure IR file.
- 1. Conventions
- 2. HTTP requests
- 3. HTTP responses
- 4. Behaviour
- 5. JSON format
- 6. Smile format
- 7. PLAIN format
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
This section assumes familiarity with HTTP concepts as defined in RFC2616 Hypertext Transfer Protocol -- HTTP/1.1.
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
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
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 valuenull
. It is recommended to add aContent-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"
.
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.
A Content-Type
header must be added if the endpoint defines a body
argument.
- If the de-aliased body type is
binary
, the Content-Typeapplication/octet-stream
must be used. - Otherwise, clients must use
application/json
.
Note that the default encoding for application/json
content type is UTF-8
.
Clients must send an Accept
header for all requests. For requests the endpoints returning binary, it must declare that the application/octet-stream
MIME type is acceptable. For all other requests, it must declare that the application/json
MIME type is acceptable. Clients may also optionally declare that the application/x-jackson-smile
MIME type is acceptable to request the server use Smile instead of JSON.
For example, the following are valid Accept
headers:
For an endpoint returning binary:
Accept: application/octet-stream
Accept: */*
For an endpoint not returning binary:
Accept: application/json
Accept: */*
Accept: application/x-jackson-smile, application/json;q=0.8
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.
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.
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.
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.
Conjure servers must respond to successful requests with HTTP status 200 OK
unless:
- the endpoint does not return a value: servers must send
204 No Content
. - the de-aliased return type is
optional<T>
and the value is not present: servers must send204 No Content
. - the de-aliased return type is a
map
,list
orset
: it is recommended to send204
but servers may send200
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.
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 - the client has indicated that the
application/x-jackson-smile
MIME type is acceptable and a non-binary value is present: servers may serialize return values using the Smile format defined below
Conjure servers must send a Content-Type
header according to the endpoint's return value:
- if the endpoint returns
204 No Content
, servers must send noContent-Type
header. - if the de-aliased return type is
binary
, servers must sendContent-Type: application/octet-stream
, - otherwise, servers must send
Content-Type: application/json
if encoding the response as JSON, - or
Content-Type: application/x-jackson-smile
if encoding the response as Smile.
In order to send a Conjure error, servers must serialize the error using the JSON format (even if the client has indicated that a Smile response is also acceptable). 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 |
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.
Servers must request reject all unexpected JSON fields. This helps developers notice bugs and mistakes quickly, instead of allowing silent failures.
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
.
Clients should be able to round trip unknown variants of enums and unions.
In order to be compatible with browser preflight requests, servers must support the HTTP OPTIONS
method.
The Conjure wire specification is compatible with HTTP/2, but it is not required.
Clients must tolerate an endpoint that is expected to return no value to return an arbitrary JSON value.
This format describes how all Conjure types are serialized into and deserialized from JSON (RFC 7159).
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, an object with any fields, or a list with any elements. |
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 . |
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. |
Conjure Union types are serialized as JSON objects with exactly two keys:
type
key - this determines the variant of the union, e.g.foo
{{variant}}
key - this key must match the variant determined above, and the value isJSON(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"]
}
Conjure Errors are serialized as JSON objects with the following keys:
errorCode
- the JSON string representation of one of the supported Conjure error codes.errorName
- a JSON string identifying the error, e.g.Recipe:RecipeNotFound
.errorInstanceId
- a JSON string containing the unique identifier,uuid
type, for this error instance.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"
}
}
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.
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.
This format describes how all Conjure types are serialized into and deserialized from Smile. Smile is designed to be a binary equivalent of JSON, so much of the encoding behavior specified for JSON carries over directly to Smile. Any differences are noted in the comments sections below.
Serializers may use optional Smile features: raw binary encoding, string deduplication, and property deduplication. The encoded data must include the standard Smile header, and may include the Smile end of stream token.
Conjure Type | Smile Type | Comments |
---|---|---|
bearertoken |
String | |
binary |
Binary | Smile natively supports binary data, so no Base64 encoding is necessary. |
boolean |
Boolean | |
datetime |
String | |
double |
Double | Non-finite values are not handled specially. |
integer |
Integer | |
rid |
String | |
safelong |
Long | |
string |
String | |
uuid |
Binary | UUIDs are encoded as 16 big-endian bytes. |
any |
N/A |
Conjure Type | Smile Type | Comments |
---|---|---|
optional<T> |
Smile(T) or Null |
|
list<T> |
Array | |
set<T> |
Array | |
map<K, V> |
Object |
Conjure Type | Smile Type | Comments |
---|---|---|
Object | Object | |
Enum | String | |
Union | Object | |
Alias(x) | Smile(x) |
Conjure Union types are serialized as Smile objects with the same structure as the JSON format.
The deserialization rules for JSON apply to Smile.
The PLAIN format describes an unquoted string 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 |