// Copyright 2025 Envoy Project Authors
//
// 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.

syntax = "proto3";

package envoy.extensions.filters.http.grpc_json_transcoder.v3;

import "google/protobuf/wrappers.proto";

import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.filters.http.grpc_json_transcoder.v3";
option java_outer_classname = "TranscoderProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/grpc_json_transcoder/v3;grpc_json_transcoderv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: gRPC-JSON transcoder]
// gRPC-JSON transcoder :ref:`configuration overview <config_http_filters_grpc_json_transcoder>`.
// [#extension: envoy.filters.http.grpc_json_transcoder]

// [#next-free-field: 18]
// GrpcJsonTranscoder filter configuration.
// The filter itself can be used per route / per virtual host or on the general level. The most
// specific one is being used for a given route. If the list of services is empty - filter
// is considered to be disabled.
// Note that if specifying the filter per route, first the route is matched, and then transcoding
// filter is applied. It matters when specifying the route configuration and paths to match the
// request - for per-route grpc transcoder configs, the original path should be matched, while
// in other cases, the grpc-like path is expected (the one AFTER the filter is applied).
message GrpcJsonTranscoder {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder";

  enum UrlUnescapeSpec {
    // URL path parameters will not decode RFC 6570 reserved characters.
    // For example, segment ``%2f%23/%20%2523`` is unescaped to ``%2f%23/ %23``.
    ALL_CHARACTERS_EXCEPT_RESERVED = 0;

    // URL path parameters will be fully URI-decoded except in
    // cases of single segment matches in reserved expansion, where ``%2F`` will be
    // left encoded.
    // For example, segment ``%2f%23/%20%2523`` is unescaped to ``%2f#/ %23``.
    ALL_CHARACTERS_EXCEPT_SLASH = 1;

    // URL path parameters will be fully URI-decoded.
    // For example, segment ``%2f%23/%20%2523`` is unescaped to ``/#/ %23``.
    ALL_CHARACTERS = 2;
  }

  // [#next-free-field: 7]
  message PrintOptions {
    option (udpa.annotations.versioning).previous_message_type =
        "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder.PrintOptions";

    // Whether to add spaces, line breaks and indentation to make the JSON
    // output easy to read. Defaults to false.
    bool add_whitespace = 1;

    // Whether to always print primitive fields. By default primitive
    // fields with default values will be omitted in JSON output. For
    // example, an int32 field set to 0 will be omitted. Setting this flag to
    // true will override the default behavior and print primitive fields
    // regardless of their values. Defaults to false.
    bool always_print_primitive_fields = 2;

    // Whether to always print enums as ints. By default they are rendered
    // as strings. Defaults to false.
    bool always_print_enums_as_ints = 3;

    // Whether to preserve proto field names. By default protobuf will
    // generate JSON field names using the ``json_name`` option, or lower camel case,
    // in that order. Setting this flag will preserve the original field names. Defaults to false.
    bool preserve_proto_field_names = 4;

    // If true, return all streams as newline-delimited JSON messages instead of as a comma-separated array
    bool stream_newline_delimited = 5;

    // If true, enforces Server-Sent Events (SSE) message framing (``data:
    // <message>\n\n``) and, ``stream_newline_delimited`` is ignored. If false,
    // message framing is determined by ``stream_newline_delimited``.
    bool stream_sse_style_delimited = 6;
  }

  message RequestValidationOptions {
    // By default, a request that cannot be mapped to any specified gRPC
    // :ref:`services <envoy_v3_api_field_extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder.services>`
    // will pass-through this filter.
    // When set to true, the request will be rejected with a ``HTTP 404 Not Found``.
    bool reject_unknown_method = 1;

    // By default, a request with query parameters that cannot be mapped to the gRPC request message
    // will pass-through this filter.
    // When set to true, the request will be rejected with a ``HTTP 400 Bad Request``.
    //
    // The fields
    // :ref:`ignore_unknown_query_parameters <envoy_v3_api_field_extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder.ignore_unknown_query_parameters>`,
    // :ref:`capture_unknown_query_parameters <envoy_v3_api_field_extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder.capture_unknown_query_parameters>`,
    // and
    // :ref:`ignored_query_parameters <envoy_v3_api_field_extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder.ignored_query_parameters>`
    // have priority over this strict validation behavior.
    bool reject_unknown_query_parameters = 2;

    // By default, transcoding will default to the value in the body if there
    // are conflict values in the bindings. For example,
    //
    // post: '/resources/a?id=123'
    // body: {
    //         id: 456,
    // }

    // "id: 456" in the body will override "id=123" in the binding.
    //
    // If this field is set to true, the request will be rejected if the binding
    // value is different from the body value.
    bool reject_binding_body_field_collisions = 3;
  }

  oneof descriptor_set {
    option (validate.required) = true;

    // Supplies the filename of
    // :ref:`the proto descriptor set <config_grpc_json_generate_proto_descriptor_set>` for the gRPC
    // services.
    string proto_descriptor = 1;

    // Supplies the binary content of
    // :ref:`the proto descriptor set <config_grpc_json_generate_proto_descriptor_set>` for the gRPC
    // services.
    bytes proto_descriptor_bin = 4;
  }

  // A list of strings that
  // supplies the fully qualified service names (i.e. "package_name.service_name") that
  // the transcoder will translate. If the service name doesn't exist in ``proto_descriptor``,
  // Envoy will fail at startup. The ``proto_descriptor`` may contain more services than
  // the service names specified here, but they won't be translated.
  //
  // By default, the filter will pass through requests that do not map to any specified services.
  // If the list of services is empty, filter is considered disabled.
  // However, this behavior changes if
  // :ref:`reject_unknown_method <envoy_v3_api_field_extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder.RequestValidationOptions.reject_unknown_method>`
  // is enabled.
  repeated string services = 2;

  // Control options for response JSON. These options are passed directly to
  // `JsonPrintOptions <https://developers.google.com/protocol-buffers/docs/reference/cpp/
  // google.protobuf.util.json_util#JsonPrintOptions>`_.
  PrintOptions print_options = 3;

  // Whether to keep the incoming request route after the outgoing headers have been transformed to
  // the match the upstream gRPC service. Note: This means that routes for gRPC services that are
  // not transcoded cannot be used in combination with ``match_incoming_request_route``.
  bool match_incoming_request_route = 5;

  // A list of query parameters to be ignored for transcoding method mapping.
  // By default, the transcoder filter will not transcode a request if there are any
  // unknown/invalid query parameters.
  //
  // Example :
  //
  // .. code-block:: proto
  //
  //     service Bookstore {
  //       rpc GetShelf(GetShelfRequest) returns (Shelf) {
  //         option (google.api.http) = {
  //           get: "/shelves/{shelf}"
  //         };
  //       }
  //     }
  //
  //     message GetShelfRequest {
  //       int64 shelf = 1;
  //     }
  //
  //     message Shelf {}
  //
  // The request ``/shelves/100?foo=bar`` will not be mapped to ``GetShelf``` because variable
  // binding for ``foo`` is not defined. Adding ``foo`` to ``ignored_query_parameters`` will allow
  // the same request to be mapped to ``GetShelf``.
  repeated string ignored_query_parameters = 6;

  // Whether to route methods without the ``google.api.http`` option.
  //
  // Example :
  //
  // .. code-block:: proto
  //
  //     package bookstore;
  //
  //     service Bookstore {
  //       rpc GetShelf(GetShelfRequest) returns (Shelf) {}
  //     }
  //
  //     message GetShelfRequest {
  //       int64 shelf = 1;
  //     }
  //
  //     message Shelf {}
  //
  // The client could ``post`` a json body ``{"shelf": 1234}`` with the path of
  // ``/bookstore.Bookstore/GetShelfRequest`` to call ``GetShelfRequest``.
  bool auto_mapping = 7;

  // Whether to ignore query parameters that cannot be mapped to a corresponding
  // protobuf field. Use this if you cannot control the query parameters and do
  // not know them beforehand. Otherwise use ``ignored_query_parameters``.
  // Defaults to false.
  bool ignore_unknown_query_parameters = 8;

  // Whether to convert gRPC status headers to JSON.
  // When trailer indicates a gRPC error and there was no HTTP body, take ``google.rpc.Status``
  // from the ``grpc-status-details-bin`` header and use it as JSON body.
  // If there was no such header, make ``google.rpc.Status`` out of the ``grpc-status`` and
  // ``grpc-message`` headers.
  // The error details types must be present in the ``proto_descriptor``.
  //
  // For example, if an upstream server replies with headers:
  //
  // .. code-block:: none
  //
  //     grpc-status: 5
  //     grpc-status-details-bin:
  //         CAUaMwoqdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUucnBjLlJlcXVlc3RJbmZvEgUKA3ItMQ
  //
  // The ``grpc-status-details-bin`` header contains a base64-encoded protobuf message
  // ``google.rpc.Status``. It will be transcoded into:
  //
  // .. code-block:: none
  //
  //     HTTP/1.1 404 Not Found
  //     content-type: application/json
  //
  //     {"code":5,"details":[{"@type":"type.googleapis.com/google.rpc.RequestInfo","requestId":"r-1"}]}
  //
  // In order to transcode the message, the ``google.rpc.RequestInfo`` type from
  // the ``google/rpc/error_details.proto`` should be included in the configured
  // :ref:`proto descriptor set <config_grpc_json_generate_proto_descriptor_set>`.
  bool convert_grpc_status = 9;

  // URL unescaping policy.
  // This spec is only applied when extracting variable with multiple segments in the URL path.
  // For example, in case of ``/foo/{x=*}/bar/{y=prefix/*}/{z=**}`` ``x`` variable is single segment and ``y`` and ``z`` are multiple segments.
  // For a path with ``/foo/first/bar/prefix/second/third/fourth``, ``x=first``, ``y=prefix/second``, ``z=third/fourth``.
  // If this setting is not specified, the value defaults to :ref:`ALL_CHARACTERS_EXCEPT_RESERVED<envoy_v3_api_enum_value_extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder.UrlUnescapeSpec.ALL_CHARACTERS_EXCEPT_RESERVED>`.
  UrlUnescapeSpec url_unescape_spec = 10 [(validate.rules).enum = {defined_only: true}];

  // If true, unescape '+' to space when extracting variables in query parameters.
  // This is to support `HTML 2.0 <https://tools.ietf.org/html/rfc1866#section-8.2.1>`_
  bool query_param_unescape_plus = 12;

  // If true, try to match the custom verb even if it is unregistered. By
  // default, only match when it is registered.
  //
  // According to the http template `syntax <https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L226-L231>`_,
  // the custom verb is **":" LITERAL** at the end of http template.
  //
  // For a request with ``/foo/bar:baz`` and ``:baz`` is not registered in any url_template, here is the behavior change
  // - if the field is not set, ``:baz`` will not be treated as custom verb, so it will match ``/foo/{x=*}``.
  // - if the field is set, ``:baz`` is treated as custom verb,  so it will NOT match ``/foo/{x=*}`` since the template doesn't use any custom verb.
  bool match_unregistered_custom_verb = 13;

  // Configure the behavior when handling requests that cannot be transcoded.
  //
  // By default, the transcoder will silently pass through HTTP requests that are malformed.
  // This includes requests with unknown query parameters, unregister paths, etc.
  //
  // Set these options to enable strict HTTP request validation, resulting in the transcoder rejecting
  // such requests with a ``HTTP 4xx``. See each individual option for more details on the validation.
  // gRPC requests will still silently pass through without transcoding.
  //
  // The benefit is a proper error message to the downstream.
  // If the upstream is a gRPC server, it cannot handle the passed-through HTTP requests and will reset
  // the TCP connection. The downstream will then
  // receive a ``HTTP 503 Service Unavailable`` due to the upstream connection reset.
  // This incorrect error message may conflict with other Envoy components, such as retry policies.
  RequestValidationOptions request_validation_options = 11;

  // Proto enum values are supposed to be in upper cases when used in JSON.
  // Set this to true if your JSON request uses non uppercase enum values.
  bool case_insensitive_enum_parsing = 14;

  // The maximum size of a request body to be transcoded, in bytes. A body exceeding this size will
  // provoke a ``HTTP 413 Request Entity Too Large`` response.
  //
  // Large values may cause envoy to use a lot of memory if there are many concurrent requests.
  //
  // If unset, the current stream buffer size is used.
  google.protobuf.UInt32Value max_request_body_size = 15 [(validate.rules).uint32 = {gt: 0}];

  // The maximum size of a response body to be transcoded, in bytes. A body exceeding this size will
  // provoke a ``HTTP 500 Internal Server Error`` response.
  //
  // Large values may cause envoy to use a lot of memory if there are many concurrent requests.
  //
  // If unset, the current stream buffer size is used.
  google.protobuf.UInt32Value max_response_body_size = 16 [(validate.rules).uint32 = {gt: 0}];

  // If true, query parameters that cannot be mapped to a corresponding
  // protobuf field are captured in an HttpBody extension of UnknownQueryParams.
  bool capture_unknown_query_parameters = 17;
}

// ``UnknownQueryParams`` is added as an extension field in ``HttpBody`` if
// ``GrpcJsonTranscoder::capture_unknown_query_parameters`` is true and unknown query
// parameters were present in the request.
message UnknownQueryParams {
  message Values {
    repeated string values = 1;
  }

  // A map from unrecognized query parameter keys, to the values associated with those keys.
  map<string, Values> key = 1;
}
