Accessing Schemas

Schema Endpoints

Let’s look at this in a browser. Run the module in a terminal window, open your favorite browser, and surf to http://localhost:5000/api;schema.

{
    "title": "Sondra-Based API",
    "definitions": {
        "filterOps": {
            "enum": [
                "with_fields",
                "count",
                "max",
                "min",
                "avg",
                "sample",
                "sum",
                "distinct",
                "contains",
                "pluck",
                "without",
                "has_fields",
                "order_by",
                "between"
            ]
        },
        "timedelta": {
            "type": "object",
            "required": [
                "start",
                "end"
            ],
            "properties": {
                "hours": {
                    "type": "integer"
                },
                "days": {
                    "type": "integer"
                },
                "seconds": {
                    "type": "number"
                },
                "minutes": {
                    "type": "integer"
                }
            }
        },
        "spatialOps": {
            "enum": [
                "distance",
                "get_intersecting",
                "get_nearest"
            ]
        }
    },
    "type": "object",
    "id": "http://localhost:5000/api;schema",
    "applications": {
        "todo-app": "http://localhost:5000/api/todo-app"
    },
    "description": "*No description provided.*"
}

That’s pretty neat! A few basic datatypes exist in the definitions, mainly to define how filtering and query-sets work. We’ll get into those later. For now, we’ll focus on what we just created. The suite schema doesn’t show us the data-type that we defined, but it does show a list of applications and their endpoints in .applications. Note that our application is no longer CamelCase, but has been turned into kabob-case. This is the default, but it can be configured otherwise. All levels of the API are turned from CamelCase or underscore_separation into kabob-case by default, for consistency, and because this is more typical of url patterns than the other case types.

Application Schema

Let’s check out the schema to todo-app. We’ll use the url in the schema followed by ;schema. Sondra is very regular about the way it treats urls. Formatting and pragmas are handled using url parameters. This is different than changing the endpoint for the file-type, which is typical of many other frameworks. The reason for this is that often, there are multiple ways to render data and keep it in the same file type. For instance, the schema and json types allow you to control the ordering and whether data is pretty-printed with lines and indentations. Other formats allow other parameters as we’ll see later on.

{
  "id": "http://localhost:5000/api/todo-app;schema",
  "definitions": {},
  "collections": {
    "items": "http://localhost:5000/api/todo-app/items"
  },
  "type": "object",
  "title": "Todo App",
  "methods": {},
  "description": "*No description provided.*"
}

This is the application schema. Once again, this is rather succinct. There’s not a lot of information in here, but if there were methods defined at the application level we would see them show up here. One can also add application level schema fragment definitions in the class and they would show up here.

Collection Schema

Let’s move on to the items themselves. Go to http://localhost:5000/api/todo-app/items;schema;indent=2.

{
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "title": "Title",
      "description": "The title of the item"
    },
    "complete": {
      "type": "boolean",
      "title": "Complete",
      "default": false
    },
    "created": {
      "type": "string",
      "title": "Created",
      "format": "date-time"
    },
    "id": {
      "type": "string",
      "title": "ID",
      "description": "The primary key."
    }
  },
  "methods": {
    "count": {
      "id": "count",
      "oneOf": [
        {
          "$ref": "#/definitions/method_request"
        },
        {
          "$ref": "#/definitions/method_response"
        }
      ],
      "title": "Object Count",
      "definitions": {
        "method_request": {
          "type": "null",
          "title": "Object Count",
          "side_effects": false,
          "description": "The number of objects in the collection."
        },
        "method_response": {
          "type": "object",
          "title": "Object Count",
          "properties": {
            "\_": {
              "type": "number"
            }
          },
          "description": "The number of objects in the collection."
        }
      },
      "description": "The number of objects in the collection."
    },
    "key-list": {
      "id": "key-list",
      "oneOf": [
        {
          "$ref": "#/definitions/method_request"
        },
        {
          "$ref": "#/definitions/method_response"
        }
      ],
      "title": "Keys",
      "definitions": {
        "method_request": {
          "type": "null",
          "title": "Keys",
          "side_effects": false,
          "description": "*No description provided*"
        },
        "method_response": {
          "type": "array",
          "title": "Keys",
          "items": {
            "type": "string"
          },
          "description": "*No description provided*"
        }
      },
      "description": "*No description provided*"
    },
    "autocomplete": {
      "id": "autocomplete",
      "oneOf": [
        {
          "$ref": "#/definitions/method_request"
        },
        {
          "$ref": "#/definitions/method_response"
        }
      ],
      "title": "Autocomplete",
      "definitions": {
        "method_request": {
          "type": "object",
          "title": "Autocomplete",
          "properties": {
            "partial": {
              "type": "string",
              "title": "Partial",
              "description": "A regular expression to match on any and all of the autocomplete fields."
            }
          },
          "side_effects": false,
          "description": "Search results based on a partial input as a regex"
        },
        "method_response": {
          "type": "array",
          "title": "Autocomplete",
          "items": {
            "type": "object",
            "properties": {
              "k": {
                "type": "string"
              },
              "v": {
                "type": "string"
              }
            }
          },
          "description": "Search results based on a partial input as a regex"
        }
      },
      "description": "Search results based on a partial input as a regex"
    },
    "key-map": {
      "id": "key-map",
      "oneOf": [
        {
          "$ref": "#/definitions/method_request"
        },
        {
          "$ref": "#/definitions/method_response"
        }
      ],
      "title": "Key Map",
      "definitions": {
        "method_request": {
          "type": "null",
          "title": "Key Map",
          "side_effects": false,
          "description": "*No description provided*"
        },
        "method_response": {
          "type": "object",
          "title": "Key Map",
          "description": "*No description provided*"
        }
      },
      "description": "*No description provided*"
    }
  },
  "definitions": {
    "filterOps": {
      "enum": [
        "with_fields",
        "count",
        "max",
        "min",
        "avg",
        "sample",
        "sum",
        "distinct",
        "contains",
        "pluck",
        "without",
        "has_fields",
        "order_by",
        "between"
      ]
    },
    "timedelta": {
      "type": "object",
      "required": [
        "start",
        "end"
      ],
      "properties": {
        "hours": {
          "type": "integer"
        },
        "days": {
          "type": "integer"
        },
        "seconds": {
          "type": "number"
        },
        "minutes": {
          "type": "integer"
        }
      }
    },
    "spatialOps": {
      "enum": [
        "distance",
        "get_intersecting",
        "get_nearest"
      ]
    }
  },
  "template": "{id}",
  "documentMethods": {
    "rel": {
      "id": "\*rel",
      "oneOf": [
        {
          "$ref": "#/definitions/method_request"
        },
        {
          "$ref": "#/definitions/method_response"
        }
      ],
      "title": "Related Documents",
      "definitions": {
        "method_request": {
          "type": "object",
          "properties": {
            "app": {
              "type": "string",
              "title": "App",
              "description": "The slug of the application ``coll`` is in."
            },
            "coll": {
              "type": "string",
              "title": "Coll",
              "description": "The slug of the collection to search for documents in."
            },
            "related_key": {
              "type": "string",
              "title": "Related Key",
              "description": "The name of the key to search for this document in.If none, defaults to the first matching foreign key element."
            }
          },
          "required": [
            "app",
            "coll"
          ],
          "title": "Related Documents",
          "description": "Reverse relation.  Get a query set of all documents in a collection that have foreign keys that point to this document.",
          "side_effects": false
        },
        "method_response": {
          "type": "object",
          "properties": {},
          "title": "Related Documents",
          "description": "Reverse relation.  Get a query set of all documents in a collection that have foreign keys that point to this document."
        }
      },
      "description": "..."
    }
  },
  "primary_key": "id",
  "description": "No Description Provided.",
  "title": "Item",
  "id": "http://localhost:5000/api/todo-app/items;schema"
}

That’s a lot more information!. This may look very dense, but it contains all the methods and inherits all the schema from the application and suite objects as well. This allows buggy JSON schema parsers to find references without having to fetch other documents in the course of resolving references.

Let’s remove the methods for a moment and see what we have:

{
  "template": "{id}",
  "primary_key": "id",
  "description": "No Description Provided.",
  "title": "Item",
  "id": "http://localhost:5000/api/todo-app/items;schema",
  "type": "object",
  "required": ["title"],
  "properties": {
    "title": {
      "type": "string",
      "title": "Title",
      "description": "The title of the item"
    },
    "complete": {
      "type": "boolean",
      "title": "Complete",
      "default": false
    },
    "created": {
      "type": "string",
      "title": "Created",
      "format": "date-time"
    },
    "id": {
      "type": "string",
      "title": "ID",
      "description": "The primary key."
    }
  }
}

JSON-Schema tells us what we’re looking at here, but just to be clear, all Sondra has done is translated the code into JSON for the client. There are a few properties that are extra to JSON Schema, such as template and primary_key. These are defined by Sondra. Template describes how one would create a visual representation for a record. Primary key lets you know what data field to use to address documents as endpoints. If you had a document with id 00ab-00ac-00ad-00ae then you would access it via the API as http://localhost:5000/api/todo-app/items/00ab-00ac-00ad-00ae.