<?xml version="1.0" encoding="UTF-8"?>
<!--
  _=_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_=
  Repose
  _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
  Copyright (C) 2010 - 2015 Rackspace US, Inc.
  _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  =_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_=_
  -->


<xs:schema xmlns:rl="http://docs.openrepose.org/repose/rate-limiting/v1.0" xmlns:live-limits="http://docs.openstack.org/common/api/v1.0"
           xmlns:html="http://www.w3.org/1999/xhtml"
           xmlns:xerces="http://xerces.apache.org"
           xmlns:saxon="http://saxon.sf.net/"
           xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified"
           attributeFormDefault="unqualified"
           targetNamespace="http://docs.openrepose.org/repose/rate-limiting/v1.0">

    <!-- Import shared types with live limits -->
    <xs:import namespace="http://docs.openstack.org/common/api/v1.0" schemaLocation="../limits/limits.xsd"/>

    <!-- Enumeration and SimpleType definitions -->
    <xs:simpleType name="StringList">
        <xs:list itemType="xs:string"/>
    </xs:simpleType>

    <xs:simpleType name="DatastoreType">
        <xs:annotation>
            <xs:documentation>
                <html:p>
                    Datastore used for storing rate limiting information.
                </html:p>
            </xs:documentation>
        </xs:annotation>

        <xs:restriction base="xs:string">
            <xs:enumeration value="local/default"/>
            <xs:enumeration value="distributed/hash-ring"/>
            <xs:enumeration value="distributed/remote"/>
        </xs:restriction>
    </xs:simpleType>

    <xs:simpleType name="HttpMethodList">
        <xs:list itemType="live-limits:HttpMethod"/>
    </xs:simpleType>

    <xs:simpleType name="PositiveInt">
        <xs:restriction base="xs:int">
            <xs:minInclusive value="0"/>
        </xs:restriction>
    </xs:simpleType>


    <!-- Configuration Schema Definitions -->
    <xs:element name="rate-limiting" type="rl:RateLimitingConfiguration"/>

    <xs:complexType name="RateLimitingConfiguration">
        <xs:annotation>
            <xs:documentation>
                <html:p>
                    A collection of rate limits.
                </html:p>
            </xs:documentation>
        </xs:annotation>

        <xs:sequence>
            <xs:element name="request-endpoint" type="rl:RequestEndpoint" minOccurs="0" maxOccurs="1"/>
            <xs:element name="global-limit-group" type="rl:GlobalLimitGroup" minOccurs="0" maxOccurs="1"/>
            <xs:element name="limit-group" type="rl:ConfiguredLimitGroup" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>

        <xs:attribute name="overLimit-429-responseCode" type="xs:boolean" use="optional" default="false">
            <xs:annotation>
                <xs:documentation>
                    <html:p>If set to true, will respond with a 429 status code. Otherwise, defaults to
                        a response of 413 status code.
                    </html:p>
                    <html:p>
                        The 429 status code was proposed in
                        <html:a href="http://tools.ietf.org/html/rfc6585">RFC-6585</html:a>
                        and indicates that the user has sent too many
                        requests in a given amount of time ("rate limiting").
                    </html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="datastore" type="rl:DatastoreType" use="optional" default="distributed/hash-ring">
            <xs:annotation>
                <xs:documentation>
                    <html:p>Name of datastore to use for storing rate limiting information.
                        If not specified, rate limiting will use the first distributed datastore available.
                        If no distributed data stores are available, rate limiting will revert to using a local
                        datastore.
                        Valid values are:
                    </html:p>
                    <html:ul>
                        <html:li>local/default</html:li>
                        <html:li>distributed/hash-ring (requires dist-datastore filter)</html:li>
                    </html:ul>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="datastore-warn-limit" type="xs:positiveInteger" use="optional" default="1000">
            <xs:annotation>
                <xs:documentation>
                    <html:p>
                        Defines limit to log a warning on size when an object is stored in to the datastore over this
                        limit. when this limit is reached, Repose will issue a warning message in the logs.
                        The limit default is 1000 count of cache keys per user (hash map as part of user key in dist
                        datastore).
                    </html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="use-capture-groups" type="xs:boolean" use="optional" default="true">
            <xs:annotation>
                <xs:documentation>
                    <html:p>
                        Determines whether or not capture groups will be used to evalute limits
                        from uri-regex. Set to false will count all the requests with the uri-regex that has
                        the capture group towards the limit count specified. By default it is set to true.
                    </html:p>
                    <html:p>
                        For example, suppose the configuration specifies a regex of /v1/(.*) with value set to 100.
                        With use-capture-groups set to false, some requests come in with a URI of /v1/pan and other
                        requests come in with a URI of /v1/cake. Each user would be allowed 50 hits if the hits
                        are evenly divided among the two URIs: 50 hits for URI /v1/pan and 50 hits for URI /v1/cake.
                    </html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>


        <xs:assert vc:minVersion="1.1" test="count(distinct-values(limit-group/@id)) = count(limit-group/@id)"
                   xerces:message="Limit groups must have unique ids"
                   saxon:message="Limit groups must have unique ids" xpathDefaultNamespace="##targetNamespace"/>

        <xs:assert vc:minVersion="1.1"
                   test="count(tokenize(string-join(limit-group/@groups,' '), ' ')) = count(distinct-values(tokenize(string-join(limit-group/@groups,' '), ' ')))"
                   xerces:message="The same group cannot be specified by multiple limit groups"
                   saxon:message="The same group cannot be specified by multiple limit groups"
                   xpathDefaultNamespace="##targetNamespace"/>

        <xs:assert vc:minVersion="1.1"
                   test="count(rl:limit-group[xs:boolean(@default)=true()]) &lt;=1"
                   xerces:message="Only one default limit group may be defined"
                   saxon:message="Only one default limit group may be defined"/>

        <xs:assert vc:minVersion="1.1" test="count(distinct-values(limit-group/limit/@id)) = count(limit-group/limit/@id)"
                   xerces:message="ALL Limits must have unique ids across all limit groups"
                   saxon:message="ALL Limits must have unique ids across all limit groups" xpathDefaultNamespace="##targetNamespace"/>

        <!-- TODO: assert at least one limit-group or global-limit-group is present -->

    </xs:complexType>

    <xs:complexType name="RequestEndpoint">
        <xs:annotation>
            <xs:documentation>
                <html:p>
                    Defines an endpoint with a matching regex to bind GET requests for
                    returning live rate limiting information.
                </html:p>
            </xs:documentation>
        </xs:annotation>

        <xs:attribute name="include-absolute-limits" type="xs:boolean" use="optional" default="false">
            <xs:annotation>
                <xs:documentation>
                    <html:p>
                        If set to true, the rate limiting component
                        marks incoming requests that match the limit request endpoint's uri-regex
                        and then passes them down to the origin service. After the origin service
                        responds with the client's absolute limits, the rate limiting component
                        enhances the response with the combined absolute and current rate limits.
                        It then sends the response to the client.
                    </html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="uri-regex" type="xs:string" use="optional" default="\/limits\/(\d*)\/">
            <xs:annotation>
                <xs:documentation>
                    <html:p>A regular expression (regex) for the URI at which the user can query their limits.</html:p>
                    <html:p>
                        Please refer to
                        <html:a href="http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html">Java
                            Regular Expression Api
                        </html:a>
                        for more information on regular expression syntax.
                    </html:p>

                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>

    <xs:complexType name="GlobalLimitGroup">
        <xs:annotation>
            <xs:documentation>
                <html:p>
                    Defines a list of rate limits to apply globally (i.e., to every request without exception).
                </html:p>
            </xs:documentation>
        </xs:annotation>

        <xs:sequence>
            <xs:element name="limit" type="rl:ConfiguredRatelimit" minOccurs="1" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>

    <xs:complexType name="ConfiguredLimitGroup">
        <xs:annotation>
            <xs:documentation>
                <html:p>Defines a list of rate limits and the groups they are associated with</html:p>

                <html:p>
                    Groups are matched on the HTTP header: X-PP-Groups
                    User information is matched on the HTTP header: X-PP-User
                </html:p>
            </xs:documentation>
        </xs:annotation>

        <xs:sequence>
            <xs:element name="limit" type="rl:ConfiguredRatelimit" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>

        <xs:attribute name="id" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                    <html:p>Unique id to identify the limit group</html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="default" type="xs:boolean" use="optional" default="false">
            <xs:annotation>
                <xs:documentation>
                    <html:p>
                        Determines whether or not this list of rate limits should
                        be considered the default when group matching fails. This limit group
                        will be applied in either of these conditions:
                    </html:p>
                    <html:ul>
                        <html:li>no group is specified</html:li>
                        <html:li>no group matches the group or groups specified</html:li>
                    </html:ul>

                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="groups" type="rl:StringList" use="required">
            <xs:annotation>
                <xs:documentation>
                    <html:p>The groups that the rate limits are associated with</html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:assert vc:minVersion="1.1" test="count(distinct-values(limit/@id)) = count(limit/@id)"
                   xerces:message="Limits must have unique ids"
                   saxon:message="Limits must have unique ids" xpathDefaultNamespace="##targetNamespace"/>

        <!--xs:assert vc:minVersion="1.1"
            test="
            count(for $first-pass in rl:limit,
            $second-pass in rl:limit[ @uri = $first-pass/@uri  and (: ids are the same :)
            @uri-regex = $first-pass/@uri-regex and (: hrefs are the same :)
            tokenize((string-join(@http-methods, ' ')), ' ') = tokenize((string-join($first-pass/@http-methods, ' ')), ' ') (: Some methods match :)
                ] return $second-pass) (: The result of this mess should equal the number of elements because every element in the for expression should match itself and only itself  :)
                = count(rl:limit)"
                xerces:message="Unique http-methods, and uri-regexes within a limit group"
                saxon:message="Unique http-methods, and uri-regexes within a limit group"/-->

    </xs:complexType>

    <xs:complexType name="ConfiguredRatelimit">
        <xs:annotation>
            <xs:documentation>
                <html:p>Defines a single rate limit</html:p>
            </xs:documentation>
        </xs:annotation>

        <!-- The "id" attribute should be prohibited, but doesn't appear present in the generated
        class is designated as such -->
        <xs:attribute name="id" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                    <html:p>
                        A placeholder for a unique identifier which will be added to any object generated from this XSD.
                        Repose will automatically assign this value when the configuration file is read.
                    </html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="uri" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                    <html:p>Human readable version of the matcher used to enforce this rate limit</html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="uri-regex" type="xs:string" use="required">
            <xs:annotation>
                <xs:documentation>
                    <html:p>The regex used to match a passing request to current limit group.
                        Within the regex, each capture group is allowed the number of hits specified
                        in the value attribute of the limit element.
                    </html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="http-methods" type="rl:HttpMethodList" use="optional" default="ALL">
            <xs:annotation>
                <xs:documentation>
                    <html:p>List of HTTP Methods this rate limit will match on.
                        Valid values include:
                        GET, DELETE, POST, PUT, PATCH, HEAD, OPTIONS, CONNECT, TRACE, ALL
                    </html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="value" type="rl:PositiveInt" use="required">
            <xs:annotation>
                <xs:documentation>
                    <html:p>Number of requests allowed within the configured unit of time
                    </html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="unit" type="live-limits:TimeUnit" use="required">
            <xs:annotation>
                <xs:documentation>
                    <html:p>Unit of time that limit enforcement should use.</html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>

        <xs:attribute name="query-param-names" type="rl:StringList" use="optional">
            <xs:annotation>
                <xs:documentation>
                    <html:p>List of query parameter names/keys that this rate limit will match on.</html:p>
                </xs:documentation>
            </xs:annotation>
        </xs:attribute>
    </xs:complexType>


</xs:schema>
