text: /* High-level gRPC client. This file is generated by Ravel.
   *
   * Example usage:
   * ```js
   *
   * // calling a remote "do_something" precedure available in your GrpcService
   * // backend service. Calling the remote method looks like this:
   *
   * service.doSomething(requestData, (err, respData) => {
   *     console.log(respData);
   * });
   * ```
   *
   * The GrpcService object takes care of serializing and deserializing the raw
   * protobuf Message objects used internally by gRPC, so don't bother trying to
   * serialize data manually.
   */
  
  const pb_2 = require('./app_pb.js');
  const {GrpcApplicationClient} = require('./app_grpc_web_pb.js');
  
  
  export const jsonSchemas = {
      {% for name, resource_class in app.manifest.resource_classes.items() %}
      "{{ name }}": {{ json_schema_generator.from_resource(resource_class, encode=True) | safe }},
      {% endfor %}
  
      {% for name, schema in nested_schemas.items() %}
      "{{ name }}": {{ json_schema_generator.from_schema(schema, encode=True) | safe }},
      {% endfor %}
  
      {% for name, schema in action_schemas.items() %}
      "{{ name }}": {{ json_schema_generator.from_schema(schema, encode=True) | safe }},
      {% endfor %}
  }
  
  
  export class Utils {
      static camel(snakeStr, capitalizeInitialChar = true) {
          var camelString = snakeStr.replace(
              /(_[a-zA-Z0-9])/g, (s) => s.toUpperCase().replace('_', '')
          )
          if (capitalizeInitialChar) {
              camelString = camelString.replace(
                  /^\w/, (s) => s.toUpperCase()
              );
          }
          return camelString;
      }
  }
  
  
  export class Schema {
      static fromJsonSchema(schemaName, jsonSchema) {
          const fields = {};
          for (const fieldName in jsonSchema.properties) {
              const jsonSchemaField = jsonSchema.properties[fieldName];
              const field = Field.fromJsonSchema(fieldName, jsonSchemaField);
              if (field !== null) {
                  fields[fieldName] = field;
              }
          }
          const schema = new Schema(schemaName, fields);
          return schema;
      }
  
      constructor(name, fields) {
          this.name = name;
          this.fields = fields;
      }
  
      load(message, context) {
          const object = {};
          for (const k in this.fields) {
              let field = this.fields[k];
              const value = field.loadFrom(message, context);
              object[field.name] = value;
          }
          if (object.hasOwnProperty('_id')) {
              object.id = object._id;
              delete object._id;
          }
          if (object.hasOwnProperty('_rev')) {
              object.rev = object._rev;
              delete object._rev;
          }
          return object;
      }
  
      dump(object, context) {
          const message = new context.messageTypes[this.name]();
          for (const k in object) {
              let field = this.fields[k];
              if (k === 'id' || k === 'rev') {
                  field = this.fields['_' + k];
                  const value = object.id;
                  if (value != null) {
                      field.dumpTo(value, message, context);
                  }
              }
              else if (field) {
                  const value = object[field.name];
                  if (value !== null && value !== undefined) {
                      field.dumpTo(value, message, context);
                  }
              }
              else{
                  // field not in schema
                  console.warn(
                      `not dumping unrecognized field: ${k}`
                  );
              }
          }
          return message;
      }
  }
  
  export class Field {
      static fromJsonSchema(fieldName, jsonSchemaField) {
          let fieldType = jsonSchemaField['type'];
          if (fieldType === undefined && jsonSchemaField['$ref'] !== undefined) {
              fieldType = 'ref';
          }
          let field = null;
          switch (fieldType) {
              case 'array':
                  const innerField = Field.fromJsonSchema(null, jsonSchemaField.items);
                  field = new ListField(fieldName, innerField);
                  break;
              case 'object':
                  field = new JsonObjectField(fieldName);
                  break;
              case 'ref':
                  const schemaName = jsonSchemaField['$ref'];
                  field = new SchemaField(fieldName, schemaName);
                  break;
              case 'integer':
              case 'string':
                  const format = jsonSchemaField.format
                  if (format === 'date-time') {
                      field = new DateTimeField(fieldName);
                  } else {
                      field = new Field(fieldName);
                  }
                  break;
              default:
                  field = new Field(fieldName);
                  break;
          }
          return field;
      }
  
      constructor(name) {
          if (name != null) {
              this.messageSetterMethodName = `set${Utils.camel(name)}`;
              this.messageGetterMethodName = `get${Utils.camel(name)}`;
          } else {
              this.messageSetterMethodName = null;
              this.messageGetterMethodName = null;
          }
          this.name = name;
      }
  
      dumpTo(value, message, context) {
          const setValueFunc = message[this.messageSetterMethodName]
          const setValue = message[this.messageSetterMethodName].bind(message)
          const dumpedValue = this.dump(value, context);
          setValue(dumpedValue);
      }
  
      loadFrom(message, context) {
          const getValue = message[this.messageGetterMethodName].bind(message);
          const value = getValue();
          return this.load(value, context);
      }
  
      static isNumber(obj) {
          return ((typeof obj) === 'number' && isFinite(obj));
      }
  
      load(value, context) {
          return value;
      }
  
      dump(value, context) {
          return value;
      }
  }
  
  
  export class DateTimeField extends Field {
      load(value, context) {
          if (Field.isNumber(value)) {
              return new Date(value * 1000);
          } else if (typeof value === 'string') {
              return new Date(value);
          } else {
              console.error(
                  `unrecognized DateTime value type ` +
                  `while loading protobuf field "${this.name}"`
              )
              return value;
          }
      }
  
      dump(value, context) {
          if (value instanceof Date) {
              return value.getTime() / 1000;
          } else if (typeof value === 'string') {
              return (new Date(value)).getTime() / 1000;
          } else if (!Field.isNumber(value)) {
              console.error(
                  `unrecognized DateTime value type ` +
                  `while dumping field "${this.name}" to profobuf`
              );
          } else {
              // at this point, it must be an int/float
              return value / 1000;
          }
      }
  }
  
  
  export class JsonObjectField extends Field {
      load(value, context) {
          return JSON.parse(value);
      }
  
      dump(value, context) {
          return JSON.stringify(value);
      }
  }
  
  
  export class ListField extends Field {
      constructor(name, innerField) {
          super(name);
          this.innerField = innerField;
      }
  
      load(values, context) {
          return values.map((x) => {
              return this.innerField.load(x, context);
          });
      }
  
      dump(values, context) {
          return values.map((x) => {
              return this.innerField.dump(x, context);
          });
      }
  }
  
  
  export class SchemaField extends Field {
      constructor(name, schemaName) {
          super(name);
          this.schemaName = schemaName
      }
  
      load(value, context) {
          const schema = context.schemas[this.schemaName];
          return schema.load(value, context);
      }
  
      dump(value, context) {
          const schema = context.schemas[this.schemaName];
          return schema.dump(value, context);
      }
  }
  
  
  export class GrpcService {
      constructor(scheme = 'http', host = 'localhost', port = 8080) {
          const serviceUrl = `${scheme}://${host}:${port}`;
          const grpcClient = new GrpcApplicationClient(serviceUrl);
  
          console.log(
              `{{ app.name }} gRPC service using server address: ${serviceUrl}`
          );
  
          // initialize aschemas and protobuf message types
          const schemas = {};
          const messageTypes = {};
          for (const schemaName in jsonSchemas) {
              const jsonSchema = jsonSchemas[schemaName];
              schemas[schemaName] = Schema.fromJsonSchema(schemaName, jsonSchema);
              if (pb_2[schemaName] !== undefined) {
                  messageTypes[schemaName] = pb_2[schemaName];
              } else {
                  console.error(`could not find proto message: ${schemaName}`);
              }
          }
  
          this.serviceUrl = serviceUrl;
          this.schemas = schemas;
          this.messageTypes = messageTypes;
          this.grpcClient = grpcClient;
      }
  
      _performRequest(
          clientMethod,
          requestSchema,
          responseSchema,
          payload,
          meta,
          callback
      ) {
          var context = {
              schemas: this.schemas,
              messageTypes: this.messageTypes
          };
          const request = requestSchema.dump(payload, context);
          clientMethod.bind(this.grpcClient)(
              request, meta, (err, response) => {
                  if (err) {
                      console.error(err);
                  } else {
                      const responseData = responseSchema.load(response, context);
                      callback(err, responseData);
                  }
              }
          );
      }
  
      {% for action in app.actions.values() %}
          /**
           {% if len(action.signature.parameters) > 1 %}
           * Data Fields:
           {% for param in list(action.signature.parameters.values())[1:] -%}
           *  - {{ param.name }}: {{ class_name(param.annotation if param.annotation != None and param.annotation != inspect._empty else 'any') | safe }}
           {% endfor %}
           *
           {% endif %}
           * @summary Calls the remote {{ action.name }} method
           * @param {!Object} request The plain old payload object
           * @param {!Function} callback Callback that resolves to response data
           * @param {?Object} meta Metadata for the RPC call
           */
          {{ action.name|camel_lower }}(
              data,
              callback,
              meta
          ) {
              this._performRequest(
                  this.grpcClient.{{ action.name }},
                  this.schemas.{{ action.name|camel }}Request,
                  this.schemas.{{ action.name|camel }}Response,
                  data, meta, callback
              );
          }
  
      {% endfor %}
  }
  