// Copyright 2021 Lightbend Inc.

// gRPC interface for the discovery service provided by user functions

syntax = "proto3";

package akkaserverless.protocol;

option go_package = "github.com/lightbend/akkaserverless-go-sdk/akkaserverless/protocol;protocol";
option java_package = "com.akkaserverless.protocol";

import "google/protobuf/descriptor.proto";
import "google/protobuf/empty.proto";

message Spec {
  // This should be the Descriptors.FileDescriptorSet in proto serialized from
  // as generated by:
  // ```
  // protoc --include_imports \
  // --proto_path=<proto file directory> \
  // --descriptor_set_out=user-function.desc \
  // <path to .proto files>
  // ```
  bytes proto = 1;
  // The components being served.
  repeated Component components = 2;
  // Optional information about the service.
  ServiceInfo service_info = 3;
}

// Information about the service that the proxy is proxying to. All of the
// information in here is optional. It may be useful for debug purposes.
message ServiceInfo {
  // The name of the service, eg, "shopping-cart".
  string service_name = 1;
  // The version of the service.
  string service_version = 2;
  // A description of the runtime for the service. Can be anything, but examples
  // might be:
  // - node v10.15.2
  // - OpenJDK Runtime Environment 1.8.0_192-b12
  string service_runtime = 3;
  // If using a support library, the name of that library, eg "akkaserverless"
  string support_library_name = 4;
  // The version of the support library being used.
  string support_library_version = 5;
  // Akka Serverless protocol major version accepted by the support library.
  int32 protocol_major_version = 6;
  // Akka Serverless protocol minor version accepted by the support library.
  int32 protocol_minor_version = 7;
}

message Component {
  // The type of component (action, event sourced entity, view, etc). By
  // convention, this should be a fully qualified service name of the service
  // which is used to call the component from the proxy on the user function,
  // for example:
  // akkaserverless.component.eventsourcedentity.EventSourcedEntities
  string component_type = 1;
  // The name of the service to load from the protobuf file.
  string service_name = 2;
  oneof component_settings {
    EntitySettings entity = 3;
    GenericComponentSettings component = 4;
  }
}

// shared settings for the components that does not have a specific settings type
message GenericComponentSettings {
  // header names to ask the proxy to forward from the request as metadata to the component calls,
  // note that the proxy may filter out Akka Serverless internal headers from this list to not leak implementation details
  repeated string forward_headers = 1;
}

// shared settings for entities (event sourced/value/replicated entity)
message EntitySettings {
  // namespace for persisted or replicated state
  string entity_type = 3;
  // Note: passivation strategy settings for value entities and event sourced entities are ignored.
  // Passivation timeout can only be configured for replicated entities.
  // Passivation strategy settings may be completely removed in future versions of the protocol.
  PassivationStrategy passivation_strategy = 4;
  // header names to ask the proxy to forward from the request as metadata to the component calls
  // note that the proxy may filter out Akka Serverless internal headers from this list to not leak implementation details
  repeated string forward_headers = 5;
  // specific settings for entities (oneof to be extensible later)
  oneof specific_settings {
    ReplicatedEntitySettings replicated_entity = 6;
  }
}

// The passivation strategy for the entity user function.
//
// The semantics is to provide a flexible way for entity user functions to
// configure the passivation strategy. This strategy is sent to the proxy at
// discovery time allowing the proxy to configure the corresponding entities.
// The only passivation strategy supported is the timeout strategy and
// configuring this is optional for the entity. If an entity user function does
// not configure the passivation strategy the proxy uses its fallback default
// value.
message PassivationStrategy {
  oneof strategy {
    // the timeout passivation strategy.
    TimeoutPassivationStrategy timeout = 1;
  }
}

// A passivation strategy based on a timeout. The idle timeout after which a
// user function's entity is passivated.
message TimeoutPassivationStrategy {
  // The timeout in millis
  int64 timeout = 1;
}

// Replicated Entity specific settings
message ReplicatedEntitySettings {
  // write consistency setting for replicating updates
  ReplicatedWriteConsistency write_consistency = 1;
}

// Write consistency setting for a replicated entity.
enum ReplicatedWriteConsistency {
  REPLICATED_WRITE_CONSISTENCY_LOCAL_UNSPECIFIED = 0;
  REPLICATED_WRITE_CONSISTENCY_MAJORITY = 1;
  REPLICATED_WRITE_CONSISTENCY_ALL = 2;
}

// An error detected by the proxy to be reported to the user.
//
// The function should attempt to load the source code for any source code locations present in the file, and use
// the information in the source code location to extract the relevant lines of code, and highlight the column with
// a caret. For example, this is what an error may look like:
//
// ERROR: AS-00023 Event sourced entity method GetCart input parameter GetCartRequest does not declare a field with an
//   entity key.
// To fix this issue, annotate one of the fields in GetCartRequest with akkaserverless.field.entity_key, for example,
//   if the first field, id, is the entity key, you might add this:
//
// string id = 1 [(akkaserverless.field).entity_key = true];
//
// At shoppingcart.proto:15:
//     rpc GetCart(GetCartRequest) returns (Cart);
//                ^
// At shoppingcart.proto:63:
// message GetCartRequest {
//     string id = 1;
// }
message UserFunctionError {
  // A single line message that describes the error.
  //
  // This message should always be displayed when the error is rendered.
  string message = 1;
  // The error code. Each class of error has a unique code.
  //
  // Typically this code should be displayed before the error message.
  string code = 2;
  // A longer message explaining the error.
  //
  // This message may be multiple lines long, may include suggestions of how to fix the error, code snippets, URLs to
  // go and read for more information, etc. If a user has configured logging to be less verbose, this detail may be
  // left off, or in a UI may be hidden in an expandable section.
  string detail = 3;
  // Zero or more source locations that are associated with this error. If the error is in a protobuf descriptor,
  // this should contain at least one location.
  repeated SourceLocation source_locations = 4;
  // A source code location.
  //
  // Source files will be protobuf files, since these are the only files that the proxy can know about.
  message SourceLocation {
    // The name of the file. This name will match the name of a file passed in a FileDescriptor returned during
    // entity discovery.
    string file_name = 1;
    // The zero based line number of the the start of the location in the file.
    int32 start_line = 2;
    // The zero based column number of the start of the location in the file.
    int32 start_col = 3;
    // The zero based line number of the end of the location in the file.
    int32 end_line = 4;
    // The zero based column number of the end of the location in the file.
    int32 end_col = 5;
    // The path of the protobuf element at this location. This can be used to map this location to an
    // actual protobuf descriptor element, if needed. The format of this field is described in
    // https://github.com/protocolbuffers/protobuf/blob/4b770cabd7ff042283280bd76b6635650a04aa8a/src/google/protobuf/descriptor.proto#L802-L825
    repeated int32 proto_path = 6;
  }
}

message ProxyInfo {
  int32 protocol_major_version = 1;
  int32 protocol_minor_version = 2;
  string proxy_name = 3;
  string proxy_version = 4;
  repeated string supported_entity_types = 5;
  bool dev_mode = 6;
  // The name that this service was deployed with
  string deployment_name = 7;
}

// Service that the SDK (in the user function) implements to allow the proxy to
// discover which components are provided by this user function
service Discovery {
  // Discover what components the user function wishes to serve.
  rpc Discover(ProxyInfo) returns (Spec);
  // Report an error back to the user function. This will only be invoked to
  // tell the user function that it has done something wrong, eg, violated the
  // protocol, tried to use a component type that isn't supported, or attempted
  // to forward to a component that doesn't exist, etc. These messages should be
  // logged clearly for debugging purposes.
  rpc ReportError(UserFunctionError) returns (google.protobuf.Empty);
  // When the Akka Serverless proxy is shutting down it sends this signal at the end
  // of the shutdown process. The user function container should preferably delay
  // its own shutdown until the proxy has terminated to be able to drain requests in
  // flight from the proxy.
  rpc ProxyTerminated(google.protobuf.Empty) returns (google.protobuf.Empty);
  // Health check for the user function to detect if it stops, crashes or becomes unresponsive
  // and the proxy needs to restart it. The SDK should respond in a timely fashion if healthy
  // or fail the request if not.
  rpc HealthCheck(google.protobuf.Empty) returns (google.protobuf.Empty);
}
