From ea79af4986b44b4daa6042b092e5f2945d13a32f Mon Sep 17 00:00:00 2001 From: "tongzhou.li" Date: Mon, 29 Jun 2026 00:20:13 +0800 Subject: [PATCH 1/3] feat: 20260629-OctBus --- .../include/google/protobuf/any.proto | 106 ++ .../include/google/protobuf/api.proto | 229 +++ .../google/protobuf/c_sharp_features.proto | 23 + .../google/protobuf/compiler/plugin.proto | 180 ++ .../google/protobuf/cpp_features.proto | 87 + .../include/google/protobuf/descriptor.proto | 1472 +++++++++++++++++ .../include/google/protobuf/duration.proto | 115 ++ .../include/google/protobuf/empty.proto | 51 + .../include/google/protobuf/field_mask.proto | 243 +++ .../include/google/protobuf/go_features.proto | 112 ++ .../google/protobuf/java_features.proto | 132 ++ .../google/protobuf/source_context.proto | 48 + .../include/google/protobuf/struct.proto | 111 ++ .../include/google/protobuf/timestamp.proto | 145 ++ .../include/google/protobuf/type.proto | 217 +++ .../include/google/protobuf/wrappers.proto | 157 ++ .tools/protoc-35.0-win64/readme.txt | 12 + HELP.md | 495 ++++++ 18 files changed, 3935 insertions(+) create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/any.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/api.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/c_sharp_features.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/compiler/plugin.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/cpp_features.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/descriptor.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/duration.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/empty.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/field_mask.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/go_features.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/java_features.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/source_context.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/struct.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/timestamp.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/type.proto create mode 100644 .tools/protoc-35.0-win64/include/google/protobuf/wrappers.proto create mode 100644 .tools/protoc-35.0-win64/readme.txt create mode 100644 HELP.md diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/any.proto b/.tools/protoc-35.0-win64/include/google/protobuf/any.proto new file mode 100644 index 00000000..e95b5b49 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/any.proto @@ -0,0 +1,106 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// In its binary encoding, an `Any` is an ordinary message; but in other wire +// forms like JSON, it has a special encoding. The format of the type URL is +// described on the `type_url` field. +// +// Protobuf APIs provide utilities to interact with `Any` values: +// +// - A 'pack' operation accepts a message and constructs a generic `Any` wrapper +// around it. +// - An 'unpack' operation reads the content of an `Any` message, either into an +// existing message or a new one. Unpack operations must check the type of the +// value they unpack against the declared `type_url`. +// - An 'is' operation decides whether an `Any` contains a message of the given +// type, i.e. whether it can 'unpack' that type. +// +// The JSON format representation of an `Any` follows one of these cases: +// +// - For types without special-cased JSON encodings, the JSON format +// representation of the `Any` is the same as that of the message, with an +// additional `@type` field which contains the type URL. +// - For types with special-cased JSON encodings (typically called 'well-known' +// types, listed in https://protobuf.dev/programming-guides/json/#any), the +// JSON format representation has a key `@type` which contains the type URL +// and a key `value` which contains the JSON-serialized value. +// +// The text format representation of an `Any` is like a message with one field +// whose name is the type URL in brackets. For example, an `Any` containing a +// `foo.Bar` message may be written `[type.googleapis.com/foo.Bar] { a: 2 }`. +message Any { + // Identifies the type of the serialized Protobuf message with a URI reference + // consisting of a prefix ending in a slash and the fully-qualified type name. + // + // Example: type.googleapis.com/google.protobuf.StringValue + // + // This string must contain at least one `/` character, and the content after + // the last `/` must be the fully-qualified name of the type in canonical + // form, without a leading dot. Do not write a scheme on these URI references + // so that clients do not attempt to contact them. + // + // The prefix is arbitrary and Protobuf implementations are expected to + // simply strip off everything up to and including the last `/` to identify + // the type. `type.googleapis.com/` is a common default prefix that some + // legacy implementations require. This prefix does not indicate the origin of + // the type, and URIs containing it are not expected to respond to any + // requests. + // + // All type URL strings must be legal URI references with the additional + // restriction (for the text format) that the content of the reference + // must consist only of alphanumeric characters, percent-encoded escapes, and + // characters in the following set (not including the outer backticks): + // `/-.~_!$&()*+,;=`. Despite our allowing percent encodings, implementations + // should not unescape them to prevent confusion with existing parsers. For + // example, `type.googleapis.com%2FFoo` should be rejected. + // + // In the original design of `Any`, the possibility of launching a type + // resolution service at these type URLs was considered but Protobuf never + // implemented one and considers contacting these URLs to be problematic and + // a potential security issue. Do not attempt to contact type URLs. + string type_url = 1; + + // Holds a Protobuf serialization of the type described by type_url. + bytes value = 2; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/api.proto b/.tools/protoc-35.0-win64/include/google/protobuf/api.proto new file mode 100644 index 00000000..c8f74254 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/api.proto @@ -0,0 +1,229 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/source_context.proto"; +import "google/protobuf/type.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "ApiProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/apipb"; + +// Api is a light-weight descriptor for an API Interface. +// +// Interfaces are also described as "protocol buffer services" in some contexts, +// such as by the "service" keyword in a .proto file, but they are different +// from API Services, which represent a concrete implementation of an interface +// as opposed to simply a description of methods and bindings. They are also +// sometimes simply referred to as "APIs" in other contexts, such as the name of +// this message itself. See https://cloud.google.com/apis/design/glossary for +// detailed terminology. +// +// New usages of this message as an alternative to ServiceDescriptorProto are +// strongly discouraged. This message does not reliability preserve all +// information necessary to model the schema and preserve semantics. Instead +// make use of FileDescriptorSet which preserves the necessary information. +message Api { + // The fully qualified name of this interface, including package name + // followed by the interface's simple name. + string name = 1; + + // The methods of this interface, in unspecified order. + repeated Method methods = 2; + + // Any metadata attached to the interface. + repeated Option options = 3; + + // A version string for this interface. If specified, must have the form + // `major-version.minor-version`, as in `1.10`. If the minor version is + // omitted, it defaults to zero. If the entire version field is empty, the + // major version is derived from the package name, as outlined below. If the + // field is not empty, the version in the package name will be verified to be + // consistent with what is provided here. + // + // The versioning schema uses [semantic + // versioning](http://semver.org) where the major version number + // indicates a breaking change and the minor version an additive, + // non-breaking change. Both version numbers are signals to users + // what to expect from different versions, and should be carefully + // chosen based on the product plan. + // + // The major version is also reflected in the package name of the + // interface, which must end in `v`, as in + // `google.feature.v1`. For major versions 0 and 1, the suffix can + // be omitted. Zero major versions must only be used for + // experimental, non-GA interfaces. + // + string version = 4; + + // Source context for the protocol buffer service represented by this + // message. + SourceContext source_context = 5; + + // Included interfaces. See [Mixin][]. + repeated Mixin mixins = 6; + + // The source syntax of the service. + Syntax syntax = 7; + + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 8; +} + +// Method represents a method of an API interface. +// +// New usages of this message as an alternative to MethodDescriptorProto are +// strongly discouraged. This message does not reliability preserve all +// information necessary to model the schema and preserve semantics. Instead +// make use of FileDescriptorSet which preserves the necessary information. +message Method { + // The simple name of this method. + string name = 1; + + // A URL of the input message type. + string request_type_url = 2; + + // If true, the request is streamed. + bool request_streaming = 3; + + // The URL of the output message type. + string response_type_url = 4; + + // If true, the response is streamed. + bool response_streaming = 5; + + // Any metadata attached to the method. + repeated Option options = 6; + + // The source syntax of this method. + // + // This field should be ignored, instead the syntax should be inherited from + // Api. This is similar to Field and EnumValue. + Syntax syntax = 7 [deprecated = true]; + + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + // + // This field should be ignored, instead the edition should be inherited from + // Api. This is similar to Field and EnumValue. + string edition = 8 [deprecated = true]; +} + +// Declares an API Interface to be included in this interface. The including +// interface must redeclare all the methods from the included interface, but +// documentation and options are inherited as follows: +// +// - If after comment and whitespace stripping, the documentation +// string of the redeclared method is empty, it will be inherited +// from the original method. +// +// - Each annotation belonging to the service config (http, +// visibility) which is not set in the redeclared method will be +// inherited. +// +// - If an http annotation is inherited, the path pattern will be +// modified as follows. Any version prefix will be replaced by the +// version of the including interface plus the [root][] path if +// specified. +// +// Example of a simple mixin: +// +// package google.acl.v1; +// service AccessControl { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/{resource=**}:getAcl"; +// } +// } +// +// package google.storage.v2; +// service Storage { +// rpc GetAcl(GetAclRequest) returns (Acl); +// +// // Get a data record. +// rpc GetData(GetDataRequest) returns (Data) { +// option (google.api.http).get = "/v2/{resource=**}"; +// } +// } +// +// Example of a mixin configuration: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// +// The mixin construct implies that all methods in `AccessControl` are +// also declared with same name and request/response types in +// `Storage`. A documentation generator or annotation processor will +// see the effective `Storage.GetAcl` method after inheriting +// documentation and annotations as follows: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/{resource=**}:getAcl"; +// } +// ... +// } +// +// Note how the version in the path pattern changed from `v1` to `v2`. +// +// If the `root` field in the mixin is specified, it should be a +// relative path under which inherited HTTP paths are placed. Example: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// root: acls +// +// This implies the following inherited HTTP annotation: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; +// } +// ... +// } +message Mixin { + // The fully qualified name of the interface which is included. + string name = 1; + + // If non-empty specifies a path under which inherited HTTP paths + // are rooted. + string root = 2; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/c_sharp_features.proto b/.tools/protoc-35.0-win64/include/google/protobuf/c_sharp_features.proto new file mode 100644 index 00000000..eb9af73b --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/c_sharp_features.proto @@ -0,0 +1,23 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2026 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +syntax = "proto2"; + +package pb; + +option csharp_namespace = "Google.Protobuf.Reflection"; +option java_multiple_files = true; +option java_outer_classname = "CSharpFeaturesOuterClass"; + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.FeatureSet { + optional CSharpFeatures csharp = 1004; +} + +message CSharpFeatures { +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/compiler/plugin.proto b/.tools/protoc-35.0-win64/include/google/protobuf/compiler/plugin.proto new file mode 100644 index 00000000..10d285f8 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/compiler/plugin.proto @@ -0,0 +1,180 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// Author: kenton@google.com (Kenton Varda) +// +// protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is +// just a program that reads a CodeGeneratorRequest from stdin and writes a +// CodeGeneratorResponse to stdout. +// +// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead +// of dealing with the raw protocol defined here. +// +// A plugin executable needs only to be placed somewhere in the path. The +// plugin should be named "protoc-gen-$NAME", and will then be used when the +// flag "--${NAME}_out" is passed to protoc. + +syntax = "proto2"; + +package google.protobuf.compiler; +option java_package = "com.google.protobuf.compiler"; +option java_outer_classname = "PluginProtos"; + +import "google/protobuf/descriptor.proto"; + +option csharp_namespace = "Google.Protobuf.Compiler"; +option go_package = "google.golang.org/protobuf/types/pluginpb"; + +// The version number of protocol compiler. +message Version { + optional int32 major = 1; + optional int32 minor = 2; + optional int32 patch = 3; + // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should + // be empty for mainline stable releases. + optional string suffix = 4; +} + +// An encoded CodeGeneratorRequest is written to the plugin's stdin. +message CodeGeneratorRequest { + // The .proto files that were explicitly listed on the command-line. The + // code generator should generate code only for these files. Each file's + // descriptor will be included in proto_file, below. + repeated string file_to_generate = 1; + + // The generator parameter passed on the command-line. + optional string parameter = 2; + + // FileDescriptorProtos for all files in files_to_generate and everything + // they import. The files will appear in topological order, so each file + // appears before any file that imports it. + // + // Note: the files listed in files_to_generate will include runtime-retention + // options only, but all other files will include source-retention options. + // The source_file_descriptors field below is available in case you need + // source-retention options for files_to_generate. + // + // protoc guarantees that all proto_files will be written after + // the fields above, even though this is not technically guaranteed by the + // protobuf wire format. This theoretically could allow a plugin to stream + // in the FileDescriptorProtos and handle them one by one rather than read + // the entire set into memory at once. However, as of this writing, this + // is not similarly optimized on protoc's end -- it will store all fields in + // memory at once before sending them to the plugin. + // + // Type names of fields and extensions in the FileDescriptorProto are always + // fully qualified. + repeated FileDescriptorProto proto_file = 15; + + // File descriptors with all options, including source-retention options. + // These descriptors are only provided for the files listed in + // files_to_generate. + repeated FileDescriptorProto source_file_descriptors = 17; + + // The version number of protocol compiler. + optional Version compiler_version = 3; +} + +// The plugin writes an encoded CodeGeneratorResponse to stdout. +message CodeGeneratorResponse { + // Error message. If non-empty, code generation failed. The plugin process + // should exit with status code zero even if it reports an error in this way. + // + // This should be used to indicate errors in .proto files which prevent the + // code generator from generating correct code. Errors which indicate a + // problem in protoc itself -- such as the input CodeGeneratorRequest being + // unparseable -- should be reported by writing a message to stderr and + // exiting with a non-zero status code. + optional string error = 1; + + // A bitmask of supported features that the code generator supports. + // This is a bitwise "or" of values from the Feature enum. + optional uint64 supported_features = 2; + + // Sync with code_generator.h. + enum Feature { + FEATURE_NONE = 0; + FEATURE_PROTO3_OPTIONAL = 1; + FEATURE_SUPPORTS_EDITIONS = 2; + } + + // The minimum edition this plugin supports. This will be treated as an + // Edition enum, but we want to allow unknown values. It should be specified + // according the edition enum value, *not* the edition number. Only takes + // effect for plugins that have FEATURE_SUPPORTS_EDITIONS set. + optional int32 minimum_edition = 3; + + // The maximum edition this plugin supports. This will be treated as an + // Edition enum, but we want to allow unknown values. It should be specified + // according the edition enum value, *not* the edition number. Only takes + // effect for plugins that have FEATURE_SUPPORTS_EDITIONS set. + optional int32 maximum_edition = 4; + + // Represents a single generated file. + message File { + // The file name, relative to the output directory. The name must not + // contain "." or ".." components and must be relative, not be absolute (so, + // the file cannot lie outside the output directory). "/" must be used as + // the path separator, not "\". + // + // If the name is omitted, the content will be appended to the previous + // file. This allows the generator to break large files into small chunks, + // and allows the generated text to be streamed back to protoc so that large + // files need not reside completely in memory at one time. Note that as of + // this writing protoc does not optimize for this -- it will read the entire + // CodeGeneratorResponse before writing files to disk. + optional string name = 1; + + // If non-empty, indicates that the named file should already exist, and the + // content here is to be inserted into that file at a defined insertion + // point. This feature allows a code generator to extend the output + // produced by another code generator. The original generator may provide + // insertion points by placing special annotations in the file that look + // like: + // @@protoc_insertion_point(NAME) + // The annotation can have arbitrary text before and after it on the line, + // which allows it to be placed in a comment. NAME should be replaced with + // an identifier naming the point -- this is what other generators will use + // as the insertion_point. Code inserted at this point will be placed + // immediately above the line containing the insertion point (thus multiple + // insertions to the same point will come out in the order they were added). + // The double-@ is intended to make it unlikely that the generated code + // could contain things that look like insertion points by accident. + // + // For example, the C++ code generator places the following line in the + // .pb.h files that it generates: + // // @@protoc_insertion_point(namespace_scope) + // This line appears within the scope of the file's package namespace, but + // outside of any particular class. Another plugin can then specify the + // insertion_point "namespace_scope" to generate additional classes or + // other declarations that should be placed in this scope. + // + // Note that if the line containing the insertion point begins with + // whitespace, the same whitespace will be added to every line of the + // inserted text. This is useful for languages like Python, where + // indentation matters. In these languages, the insertion point comment + // should be indented the same amount as any inserted code will need to be + // in order to work correctly in that context. + // + // The code generator that generates the initial file and the one which + // inserts into it must both run as part of a single invocation of protoc. + // Code generators are executed in the order in which they appear on the + // command line. + // + // If |insertion_point| is present, |name| must also be present. + optional string insertion_point = 2; + + // The file contents. + optional string content = 15; + + // Information describing the file content being inserted. If an insertion + // point is used, this information will be appropriately offset and inserted + // into the code generation metadata for the generated files. + optional GeneratedCodeInfo generated_code_info = 16; + } + repeated File file = 15; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/cpp_features.proto b/.tools/protoc-35.0-win64/include/google/protobuf/cpp_features.proto new file mode 100644 index 00000000..ba713230 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/cpp_features.proto @@ -0,0 +1,87 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +syntax = "proto2"; + +package pb; + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.FeatureSet { + optional CppFeatures cpp = 1000; +} + +message CppFeatures { + // Whether or not to treat an enum field as closed. This option is only + // applicable to enum fields, and will be removed in the future. It is + // consistent with the legacy behavior of using proto3 enum types for proto2 + // fields. + optional bool legacy_closed_enum = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + edition_deprecated: EDITION_2023, + deprecation_warning: "The legacy closed enum behavior in C++ is " + "deprecated and is scheduled to be removed in " + "edition 2025. See http://protobuf.dev/programming-guides/enum/#cpp for " + "more information", + }, + edition_defaults = { edition: EDITION_LEGACY, value: "true" }, + edition_defaults = { edition: EDITION_PROTO3, value: "false" } + ]; + + enum StringType { + STRING_TYPE_UNKNOWN = 0; + VIEW = 1; + CORD = 2; + STRING = 3; + } + + optional StringType string_type = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "STRING" }, + edition_defaults = { edition: EDITION_2024, value: "VIEW" } + ]; + + optional bool enum_name_uses_string_view = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "false" }, + edition_defaults = { edition: EDITION_2024, value: "true" } + ]; + + enum RepeatedType { + REPEATED_TYPE_UNKNOWN = 0; + // The repeated field will be backed by proto2::Repeated(Ptr)Field, and + // accessors will return a reference/pointer to this type. + LEGACY = 1; + // The repeated field has an opaque backing type, and accessors will return + // a RepeatedFieldProxy. + PROXY = 2; + } + + optional RepeatedType repeated_type = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_UNSTABLE, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LEGACY" } + ]; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/descriptor.proto b/.tools/protoc-35.0-win64/include/google/protobuf/descriptor.proto new file mode 100644 index 00000000..25dddc09 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/descriptor.proto @@ -0,0 +1,1472 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; + + // Extensions for tooling. + extensions 536000000 [declaration = { + number: 536000000 + type: ".buf.descriptor.v1.FileDescriptorSetExtension" + full_name: ".buf.descriptor.v1.buf_file_descriptor_set_extension" + }]; +} + +// The full set of known editions. +enum Edition { + // A placeholder for an unknown edition value. + EDITION_UNKNOWN = 0; + + // A placeholder edition for specifying default behaviors *before* a feature + // was first introduced. This is effectively an "infinite past". + EDITION_LEGACY = 900; + + // Legacy syntax "editions". These pre-date editions, but behave much like + // distinct editions. These can't be used to specify the edition of proto + // files, but feature definitions must supply proto2/proto3 defaults for + // backwards compatibility. + EDITION_PROTO2 = 998; + EDITION_PROTO3 = 999; + + // Editions that have been released. The specific values are arbitrary and + // should not be depended on, but they will always be time-ordered for easy + // comparison. + EDITION_2023 = 1000; + EDITION_2024 = 1001; + EDITION_2026 = 1002; + + // A placeholder edition for developing and testing unscheduled features. + EDITION_UNSTABLE = 9999; + + // Placeholder editions for testing feature resolution. These should not be + // used or relied on outside of tests. + EDITION_1_TEST_ONLY = 1; + EDITION_2_TEST_ONLY = 2; + EDITION_99997_TEST_ONLY = 99997; + EDITION_99998_TEST_ONLY = 99998; + EDITION_99999_TEST_ONLY = 99999; + + // Placeholder for specifying unbounded edition support. This should only + // ever be used by plugins that can expect to never require any changes to + // support a new edition. + EDITION_MAX = 0x7FFFFFFF; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // Names of files imported by this file purely for the purpose of providing + // option extensions. These are excluded from the dependency list above. + repeated string option_dependency = 15; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2", "proto3", and "editions". + // + // If `edition` is present, this value must be "editions". + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional string syntax = 12; + + // The edition of the proto file. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional Edition edition = 14; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; + + // Support for `export` and `local` keywords on enums. + optional SymbolVisibility visibility = 11; +} + +message ExtensionRangeOptions { + // Range reserved for first-class custom options defined by the Protobuf + // team. User custom options must use the 1000+ range instead. + extensions 990 to 998; + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + message Declaration { + // The extension number declared within the extension range. + optional int32 number = 1; + + // The fully-qualified name of the extension field. There must be a leading + // dot in front of the full name. + optional string full_name = 2; + + // The fully-qualified type name of the extension field. Unlike + // Metadata.type, Declaration.type must have a leading dot for messages + // and enums. + optional string type = 3; + + // If true, indicates that the number is reserved in the extension range, + // and any extension field with the number will fail to compile. Set this + // when a declared extension field is deleted. + optional bool reserved = 5; + + // If true, indicates that the extension must be defined as repeated. + // Otherwise the extension must be defined as optional. + optional bool repeated = 6; + + reserved 4; // removed is_repeated + } + + // For external users: DO NOT USE. We are in the process of open sourcing + // extension declaration and executing internal cleanups before it can be + // used externally. + repeated Declaration declaration = 2 [retention = RETENTION_SOURCE]; + + // Any features defined in the specific edition. + optional FeatureSet features = 50; + + // The verification state of the extension range. + enum VerificationState { + // All the extensions of the range must be declared. + DECLARATION = 0; + UNVERIFIED = 1; + } + + // The verification state of the range. + // TODO: flip the default to DECLARATION once all empty ranges + // are marked as UNVERIFIED. + optional VerificationState verification = 3 + [default = UNVERIFIED, retention = RETENTION_SOURCE]; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported after google.protobuf. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. In Editions, the group wire format + // can be enabled via the `message_encoding` feature. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REPEATED = 3; + // The required label is only allowed in google.protobuf. In proto3 and Editions + // it's explicitly prohibited. In Editions, the `field_presence` feature + // can be used to get this behavior. + LABEL_REQUIRED = 2; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; + + // If true, this is a proto3 "optional". When a proto3 field is optional, it + // tracks presence regardless of field type. + // + // When proto3_optional is true, this field must belong to a oneof to signal + // to old proto3 clients that presence is tracked for this field. This oneof + // is known as a "synthetic" oneof, and this field must be its sole member + // (each proto3 optional field gets its own synthetic oneof). Synthetic oneofs + // exist in the descriptor only, and do not generate any API. Synthetic oneofs + // must be ordered after all "real" oneofs. + // + // For message fields, proto3_optional doesn't create any semantic change, + // since non-repeated message fields always track presence. However it still + // indicates the semantic detail of whether the user wrote "optional" or not. + // This can be useful for round-tripping the .proto file. For consistency we + // give message fields a synthetic oneof also, even though it is not required + // to track presence. This is especially important because the parser can't + // tell if a field is a message or an enum, so it must always create a + // synthetic oneof. + // + // Proto2 optional fields do not set this flag, because they already indicate + // optional with `LABEL_OPTIONAL`. + optional bool proto3_optional = 17; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; + + // Support for `export` and `local` keywords on enums. + optional SymbolVisibility visibility = 6; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; + + reserved 4; + reserved "stream"; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + // Controls the name of the wrapper Java class generated for the .proto file. + // That class will always contain the .proto file's getDescriptor() method as + // well as any top-level extensions defined in the .proto file. + // If java_multiple_files is disabled, then all the other classes from the + // .proto file will be nested inside the single wrapper outer class. + optional string java_outer_classname = 8; + + // If enabled, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the wrapper class + // named by java_outer_classname. However, the wrapper class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [ + default = false, + feature_support = { + edition_introduced: EDITION_PROTO2 + edition_removed: EDITION_2024 + removal_error: "This behavior is enabled by default in editions 2024 and above. " + "To disable it, you can set `features.(pb.java).nest_in_file_class = YES` " + "on individual messages, enums, or services." + + } + ]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // A proto2 file can set this to true to opt in to UTF-8 checking for Java, + // which will throw an exception if invalid UTF-8 is parsed from the wire or + // assigned to a string field. + // + // TODO: clarify exactly what kinds of field types this option + // applies to, and update these docs accordingly. + // + // Proto3 files already perform these checks. Setting the option explicitly to + // false has no effect: it cannot be used to opt proto3 files out of UTF-8 + // checks. + optional bool java_string_check_utf8 = 27 [default = false]; + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + reserved 42; // removed php_generic_services + reserved "php_generic_services"; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = true]; + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 50; + + // Range reserved for first-class custom options defined by the Protobuf + // team. User custom options must use the 1000+ range instead. + extensions 990 to 998 [declaration = { + number: 990, + full_name: ".pb.file.cpp", + type: ".pb.file.CppFileOptions" + }]; + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + reserved 4, 5, 6; + + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // + // This should only be used as a temporary measure against broken builds due + // to the change in behavior for JSON field name conflicts. + // + // TODO This is legacy behavior we plan to remove once downstream + // teams have had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated = true]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 12; + + // Range reserved for first-class custom options defined by the Protobuf + // team. User custom options must use the 1000+ range instead. + extensions 990 to 998; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead. + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is only implemented to support use of + // [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of + // type "bytes" in the open source release. + // TODO: make ctype actually deprecated. + optional CType ctype = 1 [/*deprecated = true,*/ default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + // The option [ctype=CORD] may be applied to a non-repeated field of type + // "bytes". It indicates that in C++, the data should be stored in a Cord + // instead of a string. For very large strings, this may reduce memory + // fragmentation. It may also allow better performance when parsing from a + // Cord, or when parsing with aliasing enabled, as the parsed Cord may then + // alias the original buffer. + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. This option is prohibited in + // Editions, but the `repeated_field_encoding` feature can be used to control + // the behavior. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // Note that lazy message fields are still eagerly verified to check + // ill-formed wireformat or missing required fields. Calling IsInitialized() + // on the outer message would fail if the inner message has missing required + // fields. Failed verification would result in parsing failure (except when + // uninitialized messages are acceptable). + optional bool lazy = 5 [default = false]; + + // unverified_lazy does no correctness checks on the byte stream. This should + // only be used where lazy with verification is prohibitive for performance + // reasons. + optional bool unverified_lazy = 15 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // DEPRECATED. DO NOT USE! + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false, deprecated = true]; + + // Indicate that the field value should not be printed out when using debug + // formats, e.g. when the field contains sensitive credentials. + optional bool debug_redact = 16 [default = false]; + + // If set to RETENTION_SOURCE, the option will be omitted from the binary. + enum OptionRetention { + RETENTION_UNKNOWN = 0; + RETENTION_RUNTIME = 1; + RETENTION_SOURCE = 2; + } + + optional OptionRetention retention = 17; + + // This indicates the types of entities that the field may apply to when used + // as an option. If it is unset, then the field may be freely used as an + // option on any kind of entity. + enum OptionTargetType { + TARGET_TYPE_UNKNOWN = 0; + TARGET_TYPE_FILE = 1; + TARGET_TYPE_EXTENSION_RANGE = 2; + TARGET_TYPE_MESSAGE = 3; + TARGET_TYPE_FIELD = 4; + TARGET_TYPE_ONEOF = 5; + TARGET_TYPE_ENUM = 6; + TARGET_TYPE_ENUM_ENTRY = 7; + TARGET_TYPE_SERVICE = 8; + TARGET_TYPE_METHOD = 9; + } + + repeated OptionTargetType targets = 19; + + message EditionDefault { + optional Edition edition = 3; + optional string value = 2; // Textproto value. + } + repeated EditionDefault edition_defaults = 20; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 21; + + // Information about the support window of a feature. + message FeatureSupport { + // The edition that this feature was first available in. In editions + // earlier than this one, the default assigned to EDITION_LEGACY will be + // used, and proto files will not be able to override it. + optional Edition edition_introduced = 1; + + // The edition this feature becomes deprecated in. Using this after this + // edition may trigger warnings. + optional Edition edition_deprecated = 2; + + // The deprecation warning text if this feature is used after the edition it + // was marked deprecated in. + optional string deprecation_warning = 3; + + // The edition this feature is no longer available in. In editions after + // this one, the last default assigned will be used, and proto files will + // not be able to override it. + optional Edition edition_removed = 4; + + // The removal error text if this feature is used after the edition it was + // removed in. + optional string removal_error = 5; + } + optional FeatureSupport feature_support = 22; + + // Range reserved for first-class custom options defined by the Protobuf + // team. User custom options must use the 1000+ range instead. + extensions 990 to 998; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype + reserved 18; // reserve target, target_obsolete_do_not_use +} + +message OneofOptions { + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 1; + + // Range reserved for first-class custom options defined by the Protobuf + // team. User custom options must use the 1000+ range instead. + extensions 990 to 998; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // TODO Remove this legacy behavior once downstream teams have + // had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated = true]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 7; + + // Range reserved for first-class custom options defined by the Protobuf + // team. User custom options must use the 1000+ range instead. + extensions 990 to 998; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 2; + + // Indicate that fields annotated with this enum value should not be printed + // out when using debug formats, e.g. when the field contains sensitive + // credentials. + optional bool debug_redact = 3 [default = false]; + + // Information about the support window of a feature value. + optional FieldOptions.FeatureSupport feature_support = 4; + + // Range reserved for first-class extension options defined by the Protobuf + // team. Custom options must use the 1000+ range instead. + extensions 990 to 998; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 34; + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // Range reserved for first-class custom options defined by the Protobuf + // team. User custom options must use the 1000+ range instead. + extensions 990 to 998; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 35; + + // Range reserved for first-class custom options defined by the Protobuf + // team. User custom options must use the 1000+ range instead. + extensions 990 to 998; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents + // "foo.(bar.baz).moo". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Features + +// TODO Enums in C++ gencode (and potentially other languages) are +// not well scoped. This means that each of the feature enums below can clash +// with each other. The short names we've chosen maximize call-site +// readability, but leave us very open to this scenario. A future feature will +// be designed and implemented to handle this, hopefully before we ever hit a +// conflict here. +message FeatureSet { + enum FieldPresence { + FIELD_PRESENCE_UNKNOWN = 0; + EXPLICIT = 1; + IMPLICIT = 2; + LEGACY_REQUIRED = 3; + } + optional FieldPresence field_presence = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPLICIT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "IMPLICIT" }, + edition_defaults = { edition: EDITION_2023, value: "EXPLICIT" } + ]; + + enum EnumType { + ENUM_TYPE_UNKNOWN = 0; + OPEN = 1; + CLOSED = 2; + } + optional EnumType enum_type = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "CLOSED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "OPEN" } + ]; + + enum RepeatedFieldEncoding { + REPEATED_FIELD_ENCODING_UNKNOWN = 0; + PACKED = 1; + EXPANDED = 2; + } + optional RepeatedFieldEncoding repeated_field_encoding = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPANDED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "PACKED" } + ]; + + enum Utf8Validation { + UTF8_VALIDATION_UNKNOWN = 0; + VERIFY = 2; + NONE = 3; + reserved 1; + } + optional Utf8Validation utf8_validation = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "NONE" }, + edition_defaults = { edition: EDITION_PROTO3, value: "VERIFY" } + ]; + + enum MessageEncoding { + MESSAGE_ENCODING_UNKNOWN = 0; + LENGTH_PREFIXED = 1; + DELIMITED = 2; + } + optional MessageEncoding message_encoding = 5 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LENGTH_PREFIXED" } + ]; + + enum JsonFormat { + JSON_FORMAT_UNKNOWN = 0; + ALLOW = 1; + LEGACY_BEST_EFFORT = 2; + } + optional JsonFormat json_format = 6 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LEGACY_BEST_EFFORT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "ALLOW" } + ]; + + enum EnforceNamingStyle { + ENFORCE_NAMING_STYLE_UNKNOWN = 0; + STYLE2024 = 1; + STYLE_LEGACY = 2; + STYLE2026 = 3; + } + optional EnforceNamingStyle enforce_naming_style = 7 [ + retention = RETENTION_SOURCE, + targets = TARGET_TYPE_FILE, + targets = TARGET_TYPE_EXTENSION_RANGE, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_ONEOF, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_ENUM_ENTRY, + targets = TARGET_TYPE_SERVICE, + targets = TARGET_TYPE_METHOD, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "STYLE_LEGACY" }, + edition_defaults = { edition: EDITION_2024, value: "STYLE2024" }, + edition_defaults = { edition: EDITION_UNSTABLE, value: "STYLE2026" } + ]; + + message VisibilityFeature { + enum DefaultSymbolVisibility { + DEFAULT_SYMBOL_VISIBILITY_UNKNOWN = 0; + + // Default pre-EDITION_2024, all UNSET visibility are export. + EXPORT_ALL = 1; + + // All top-level symbols default to export, nested default to local. + EXPORT_TOP_LEVEL = 2; + + // All symbols default to local. + LOCAL_ALL = 3; + + // All symbols local by default. Nested types cannot be exported. + // With special case caveat for message { enum {} reserved 1 to max; } + // This is the recommended setting for new protos. + STRICT = 4; + } + reserved 1 to max; + } + optional VisibilityFeature.DefaultSymbolVisibility default_symbol_visibility = + 8 [ + retention = RETENTION_SOURCE, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPORT_ALL" }, + edition_defaults = { edition: EDITION_2024, value: "EXPORT_TOP_LEVEL" } + ]; + + reserved 999; + + extensions 1000 to 9994 [ + declaration = { + number: 1000, + full_name: ".pb.cpp", + type: ".pb.CppFeatures" + }, + declaration = { + number: 1001, + full_name: ".pb.java", + type: ".pb.JavaFeatures" + }, + declaration = { number: 1002, full_name: ".pb.go", type: ".pb.GoFeatures" }, + declaration = { + number: 1003, + full_name: ".pb.python", + type: ".pb.PythonFeatures" + }, + declaration = { + number: 1004, + full_name: ".pb.csharp", + type: ".pb.CSharpFeatures" + }, + declaration = { + number: 1100, + full_name: ".imp.impress_feature_set", + type: ".imp.ImpressFeatureSet" + }, + declaration = { + number: 9989, + full_name: ".pb.java_mutable", + type: ".pb.JavaMutableFeatures" + }, + declaration = { + number: 9990, + full_name: ".pb.proto1", + type: ".pb.Proto1Features" + } + ]; + + extensions 9995 to 9999; // For internal testing + extensions 10000; // for https://github.com/bufbuild/protobuf-es +} + +// A compiled specification for the defaults of a set of features. These +// messages are generated from FeatureSet extensions and can be used to seed +// feature resolution. The resolution with this object becomes a simple search +// for the closest matching edition, followed by proto merges. +message FeatureSetDefaults { + // A map from every known edition with a unique set of defaults to its + // defaults. Not all editions may be contained here. For a given edition, + // the defaults at the closest matching edition ordered at or before it should + // be used. This field must be in strict ascending order by edition. + message FeatureSetEditionDefault { + optional Edition edition = 3; + + // Defaults of features that can be overridden in this edition. + optional FeatureSet overridable_features = 4; + + // Defaults of features that can't be overridden in this edition. + optional FeatureSet fixed_features = 5; + + reserved 1, 2; + reserved "features"; + } + repeated FeatureSetEditionDefault defaults = 1; + + // The minimum supported edition (inclusive) when this was constructed. + // Editions before this will not have defaults. + optional Edition minimum_edition = 4; + + // The maximum known edition (inclusive) when this was constructed. Editions + // after this will not have reliable defaults. + optional Edition maximum_edition = 5; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition appears. + // For example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to moo. + // // + // // Another line attached to moo. + // optional double moo = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to moo or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } + + // Extensions for tooling. + extensions 536000000 [declaration = { + number: 536000000 + type: ".buf.descriptor.v1.SourceCodeInfoExtension" + full_name: ".buf.descriptor.v1.buf_source_code_info_extension" + }]; +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified object. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + + // Represents the identified object's effect on the element in the original + // .proto file. + enum Semantic { + // There is no effect or the effect is indescribable. + NONE = 0; + // The element is set or otherwise mutated. + SET = 1; + // An alias to the element is returned. + ALIAS = 2; + } + optional Semantic semantic = 5; + } +} + +// Describes the 'visibility' of a symbol with respect to the proto import +// system. Symbols can only be imported when the visibility rules do not prevent +// it (ex: local symbols cannot be imported). Visibility modifiers can only set +// on `message` and `enum` as they are the only types available to be referenced +// from other files. +enum SymbolVisibility { + VISIBILITY_UNSET = 0; + VISIBILITY_LOCAL = 1; + VISIBILITY_EXPORT = 2; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/duration.proto b/.tools/protoc-35.0-win64/include/google/protobuf/duration.proto new file mode 100644 index 00000000..41f40c22 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/duration.proto @@ -0,0 +1,115 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/durationpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/empty.proto b/.tools/protoc-35.0-win64/include/google/protobuf/empty.proto new file mode 100644 index 00000000..b87c89dc --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/empty.proto @@ -0,0 +1,51 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/emptypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; + +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// +message Empty {} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/field_mask.proto b/.tools/protoc-35.0-win64/include/google/protobuf/field_mask.proto new file mode 100644 index 00000000..7093fba5 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/field_mask.proto @@ -0,0 +1,243 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "FieldMaskProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; +option cc_enable_arenas = true; + +// `FieldMask` represents a set of symbolic field paths, for example: +// +// paths: "f.a" +// paths: "f.b.d" +// +// Here `f` represents a field in some root message, `a` and `b` +// fields in the message found in `f`, and `d` a field found in the +// message in `f.b`. +// +// Field masks are used to specify a subset of fields that should be +// returned by a get operation or modified by an update operation. +// Field masks also have a custom JSON encoding (see below). +// +// # Field Masks in Projections +// +// When used in the context of a projection, a response message or +// sub-message is filtered by the API to only contain those fields as +// specified in the mask. For example, if the mask in the previous +// example is applied to a response message as follows: +// +// f { +// a : 22 +// b { +// d : 1 +// x : 2 +// } +// y : 13 +// } +// z: 8 +// +// The result will not contain specific values for fields x,y and z +// (their value will be set to the default, and omitted in proto text +// output): +// +// +// f { +// a : 22 +// b { +// d : 1 +// } +// } +// +// A repeated field is not allowed except at the last position of a +// paths string. +// +// If a FieldMask object is not present in a get operation, the +// operation applies to all fields (as if a FieldMask of all fields +// had been specified). +// +// Note that a field mask does not necessarily apply to the +// top-level response message. In case of a REST get operation, the +// field mask applies directly to the response, but in case of a REST +// list operation, the mask instead applies to each individual message +// in the returned resource list. In case of a REST custom method, +// other definitions may be used. Where the mask applies will be +// clearly documented together with its declaration in the API. In +// any case, the effect on the returned resource/resources is required +// behavior for APIs. +// +// # Field Masks in Update Operations +// +// A field mask in update operations specifies which fields of the +// targeted resource are going to be updated. The API is required +// to only change the values of the fields as specified in the mask +// and leave the others untouched. If a resource is passed in to +// describe the updated values, the API ignores the values of all +// fields not covered by the mask. +// +// If a repeated field is specified for an update operation, new values will +// be appended to the existing repeated field in the target resource. Note that +// a repeated field is only allowed in the last position of a `paths` string. +// +// If a sub-message is specified in the last position of the field mask for an +// update operation, then new value will be merged into the existing sub-message +// in the target resource. +// +// For example, given the target message: +// +// f { +// b { +// d: 1 +// x: 2 +// } +// c: [1] +// } +// +// And an update message: +// +// f { +// b { +// d: 10 +// } +// c: [2] +// } +// +// then if the field mask is: +// +// paths: ["f.b", "f.c"] +// +// then the result will be: +// +// f { +// b { +// d: 10 +// x: 2 +// } +// c: [1, 2] +// } +// +// An implementation may provide options to override this default behavior for +// repeated and message fields. +// +// Note that libraries which implement FieldMask resolution have various +// different behaviors in the face of empty masks or the special "*" mask. +// When implementing a service you should confirm these cases have the +// appropriate behavior in the underlying FieldMask library that you desire, +// and you may need to special case those cases in your application code if +// the underlying field mask library behavior differs from your intended +// service semantics. +// +// Update methods implementing https://google.aip.dev/134 +// - MUST support the special value * meaning "full replace" +// - MUST treat an omitted field mask as "replace fields which are present". +// +// Other methods implementing https://google.aip.dev/157 +// - SHOULD support the special value "*" to mean "get all". +// - MUST treat an omitted field mask to mean "get all", unless otherwise +// documented. +// +// ## Considerations for HTTP REST +// +// The HTTP kind of an update operation which uses a field mask must +// be set to PATCH instead of PUT in order to satisfy HTTP semantics +// (PUT must only be used for full updates). +// +// # JSON Encoding of Field Masks +// +// In JSON, a field mask is encoded as a single string where paths are +// separated by a comma. Fields name in each path are converted +// to/from lower-camel naming conventions. +// +// As an example, consider the following message declarations: +// +// message Profile { +// User user = 1; +// Photo photo = 2; +// } +// message User { +// string display_name = 1; +// string address = 2; +// } +// +// In proto a field mask for `Profile` may look as such: +// +// mask { +// paths: "user.display_name" +// paths: "photo" +// } +// +// In JSON, the same mask is represented as below: +// +// { +// mask: "user.displayName,photo" +// } +// +// # Field Masks and Oneof Fields +// +// Field masks treat fields in oneofs just as regular fields. Consider the +// following message: +// +// message SampleMessage { +// oneof test_oneof { +// string name = 4; +// SubMessage sub_message = 9; +// } +// } +// +// The field mask can be: +// +// mask { +// paths: "name" +// } +// +// Or: +// +// mask { +// paths: "sub_message" +// } +// +// Note that oneof type names ("test_oneof" in this case) cannot be used in +// paths. +// +// ## Field Mask Verification +// +// The implementation of any API method which has a FieldMask type field in the +// request should verify the included field paths, and return an +// `INVALID_ARGUMENT` error if any path is unmappable. +message FieldMask { + // The set of field mask paths. + repeated string paths = 1; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/go_features.proto b/.tools/protoc-35.0-win64/include/google/protobuf/go_features.proto new file mode 100644 index 00000000..13819acd --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/go_features.proto @@ -0,0 +1,112 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +syntax = "proto2"; + +package pb; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/protobuf/types/gofeaturespb"; + +extend google.protobuf.FeatureSet { + optional GoFeatures go = 1002; +} + +message GoFeatures { + // Whether or not to generate the deprecated UnmarshalJSON method for enums. + // Can only be true for proto using the Open Struct api. + optional bool legacy_unmarshal_json_enum = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + edition_deprecated: EDITION_2023, + deprecation_warning: "The legacy UnmarshalJSON API is deprecated and " + "will be removed in a future edition.", + }, + edition_defaults = { edition: EDITION_LEGACY, value: "true" }, + edition_defaults = { edition: EDITION_PROTO3, value: "false" } + ]; + + enum APILevel { + // API_LEVEL_UNSPECIFIED results in selecting the OPEN API, + // but needs to be a separate value to distinguish between + // an explicitly set api level or a missing api level. + API_LEVEL_UNSPECIFIED = 0; + API_OPEN = 1; + API_HYBRID = 2; + API_OPAQUE = 3; + } + + // One of OPEN, HYBRID or OPAQUE. + optional APILevel api_level = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { + edition: EDITION_LEGACY, + value: "API_LEVEL_UNSPECIFIED" + }, + edition_defaults = { edition: EDITION_2024, value: "API_OPAQUE" } + ]; + + enum StripEnumPrefix { + STRIP_ENUM_PREFIX_UNSPECIFIED = 0; + STRIP_ENUM_PREFIX_KEEP = 1; + STRIP_ENUM_PREFIX_GENERATE_BOTH = 2; + STRIP_ENUM_PREFIX_STRIP = 3; + } + + optional StripEnumPrefix strip_enum_prefix = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_ENUM_ENTRY, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2024, + }, + // TODO: change the default to STRIP_ENUM_PREFIX_STRIP for edition 2025. + edition_defaults = { + edition: EDITION_LEGACY, + value: "STRIP_ENUM_PREFIX_KEEP" + } + ]; + + // Wrap the OptimizeMode enum in a message for scoping: + // This way, users can type shorter names (SPEED, CODE_SIZE). + message OptimizeModeFeature { + // The name of this enum matches OptimizeMode in descriptor.proto. + enum OptimizeMode { + // OPTIMIZE_MODE_UNSPECIFIED results in falling back to the default + // (optimize for code size), but needs to be a separate value to distinguish + // between an explicitly set optimize mode or a missing optimize mode. + OPTIMIZE_MODE_UNSPECIFIED = 0; + SPEED = 1; + CODE_SIZE = 2; + // There is no enum entry for LITE_RUNTIME (descriptor.proto), + // because Go Protobuf does not have the concept of a lite runtime. + } + } + + optional OptimizeModeFeature.OptimizeMode optimize_mode = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { + edition: EDITION_LEGACY, + value: "OPTIMIZE_MODE_UNSPECIFIED" + } + ]; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/java_features.proto b/.tools/protoc-35.0-win64/include/google/protobuf/java_features.proto new file mode 100644 index 00000000..80ac6fa9 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/java_features.proto @@ -0,0 +1,132 @@ + +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +syntax = "proto2"; + +package pb; + +import "google/protobuf/descriptor.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "JavaFeaturesProto"; + +extend google.protobuf.FeatureSet { + optional JavaFeatures java = 1001; +} + +message JavaFeatures { + // Whether or not to treat an enum field as closed. This option is only + // applicable to enum fields, and will be removed in the future. It is + // consistent with the legacy behavior of using proto3 enum types for proto2 + // fields. + optional bool legacy_closed_enum = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + edition_deprecated: EDITION_2023, + deprecation_warning: "The legacy closed enum behavior in Java is " + "deprecated and is scheduled to be removed in " + "edition 2025. See http://protobuf.dev/programming-guides/enum/#java for " + "more information.", + }, + edition_defaults = { edition: EDITION_LEGACY, value: "true" }, + edition_defaults = { edition: EDITION_PROTO3, value: "false" } + ]; + + // The UTF8 validation strategy to use. + enum Utf8Validation { + // Invalid default, which should never be used. + UTF8_VALIDATION_UNKNOWN = 0; + // Respect the UTF8 validation behavior specified by the global + // utf8_validation feature. + DEFAULT = 1; + // Verifies UTF8 validity overriding the global utf8_validation + // feature. This represents the legacy java_string_check_utf8 option. + VERIFY = 2; + } + optional Utf8Validation utf8_validation = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + edition_deprecated: EDITION_2024, + deprecation_warning: "The Java-specific utf8 validation feature is " + "deprecated and is scheduled to be removed in " + "edition 2025. Utf8 validation behavior should " + "use the global cross-language utf8_validation " + "feature.", + }, + edition_defaults = { edition: EDITION_LEGACY, value: "DEFAULT" } + ]; + + // Allows creation of large Java enums, extending beyond the standard + // constant limits imposed by the Java language. + optional bool large_enum = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "false" } + ]; + + // Whether to use the old default outer class name scheme, or the new feature + // which adds a "Proto" suffix to the outer class name. + // + // Users will not be able to set this option, because we removed it in the + // same edition that it was introduced. But we use it to determine which + // naming scheme to use for outer class name defaults. + optional bool use_old_outer_classname_default = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2024, + edition_removed: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "true" }, + edition_defaults = { edition: EDITION_2024, value: "false" } + ]; + + message NestInFileClassFeature { + enum NestInFileClass { + // Invalid default, which should never be used. + NEST_IN_FILE_CLASS_UNKNOWN = 0; + // Do not nest the generated class in the file class. + NO = 1; + // Nest the generated class in the file class. + YES = 2; + // Fall back to the `java_multiple_files` option. Users won't be able to + // set this option. + LEGACY = 3 [feature_support = { + edition_introduced: EDITION_2024 + edition_removed: EDITION_2024 + }]; + } + reserved 1 to max; + } + + // Whether to nest the generated class in the generated file class. This is + // only applicable to *top-level* messages, enums, and services. + optional NestInFileClassFeature.NestInFileClass nest_in_file_class = 5 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_SERVICE, + feature_support = { + edition_introduced: EDITION_2024, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LEGACY" }, + edition_defaults = { edition: EDITION_2024, value: "NO" } + ]; + + reserved 6; // field `mutable_nest_in_file_class` removed. +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/source_context.proto b/.tools/protoc-35.0-win64/include/google/protobuf/source_context.proto new file mode 100644 index 00000000..135f50fe --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/source_context.proto @@ -0,0 +1,48 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "SourceContextProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; + +// `SourceContext` represents information about the source of a +// protobuf element, like the file in which it is defined. +message SourceContext { + // The path-qualified name of the .proto file that contained the associated + // protobuf element. For example: `"google/protobuf/source_context.proto"`. + string file_name = 1; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/struct.proto b/.tools/protoc-35.0-win64/include/google/protobuf/struct.proto new file mode 100644 index 00000000..a03667da --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/struct.proto @@ -0,0 +1,111 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/structpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "StructProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// Represents a JSON object. +// +// An unordered key-value map, intending to perfectly capture the semantics of a +// JSON object. This enables parsing any arbitrary JSON payload as a message +// field in ProtoJSON format. +// +// This follows RFC 8259 guidelines for interoperable JSON: notably this type +// cannot represent large Int64 values or `NaN`/`Infinity` numbers, +// since the JSON format generally does not support those values in its number +// type. +// +// If you do not intend to parse arbitrary JSON into your message, a custom +// typed message should be preferred instead of using this type. +message Struct { + // Unordered map of dynamically typed values. + map fields = 1; +} + +// Represents a JSON value. +// +// `Value` represents a dynamically typed value which can be either +// null, a number, a string, a boolean, a recursive struct value, or a +// list of values. A producer of value is expected to set one of these +// variants. Absence of any variant is an invalid state. +message Value { + // The kind of value. + oneof kind { + // Represents a JSON `null`. + NullValue null_value = 1; + + // Represents a JSON number. Must not be `NaN`, `Infinity` or + // `-Infinity`, since those are not supported in JSON. This also cannot + // represent large Int64 values, since JSON format generally does not + // support them in its number type. + double number_value = 2; + + // Represents a JSON string. + string string_value = 3; + + // Represents a JSON boolean (`true` or `false` literal in JSON). + bool bool_value = 4; + + // Represents a JSON object. + Struct struct_value = 5; + + // Represents a JSON array. + ListValue list_value = 6; + } +} + +// Represents a JSON `null`. +// +// `NullValue` is a sentinel, using an enum with only one value to represent +// the null value for the `Value` type union. +// +// A field of type `NullValue` with any value other than `0` is considered +// invalid. Most ProtoJSON serializers will emit a Value with a `null_value` set +// as a JSON `null` regardless of the integer value, and so will round trip to +// a `0` value. +enum NullValue { + // Null value. + NULL_VALUE = 0; +} + +// Represents a JSON array. +message ListValue { + // Repeated field of dynamically typed values. + repeated Value values = 1; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/timestamp.proto b/.tools/protoc-35.0-win64/include/google/protobuf/timestamp.proto new file mode 100644 index 00000000..6bc1efc6 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/timestamp.proto @@ -0,0 +1,145 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/timestamppb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// Example 5: Compute Timestamp from Java `Instant.now()`. +// +// Instant now = Instant.now(); +// +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); +// +// Example 6: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A ProtoJSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a ProtoJSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() +// ) to obtain a formatter capable of generating timestamps in this format. +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must + // be between -62135596800 and 253402300799 inclusive (which corresponds to + // 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z). + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. This field is + // the nanosecond portion of the duration, not an alternative to seconds. + // Negative second values with fractions must still have non-negative nanos + // values that count forward in time. Must be between 0 and 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/type.proto b/.tools/protoc-35.0-win64/include/google/protobuf/type.proto new file mode 100644 index 00000000..2c7615ed --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/type.proto @@ -0,0 +1,217 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/any.proto"; +import "google/protobuf/source_context.proto"; + +option cc_enable_arenas = true; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TypeProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/typepb"; + +// A protocol buffer message type. +// +// New usages of this message as an alternative to DescriptorProto are strongly +// discouraged. This message does not reliability preserve all information +// necessary to model the schema and preserve semantics. Instead make use of +// FileDescriptorSet which preserves the necessary information. +message Type { + // The fully qualified message name. + string name = 1; + // The list of fields. + repeated Field fields = 2; + // The list of types appearing in `oneof` definitions in this type. + repeated string oneofs = 3; + // The protocol buffer options. + repeated Option options = 4; + // The source context. + SourceContext source_context = 5; + // The source syntax. + Syntax syntax = 6; + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 7; +} + +// A single field of a message type. +// +// New usages of this message as an alternative to FieldDescriptorProto are +// strongly discouraged. This message does not reliability preserve all +// information necessary to model the schema and preserve semantics. Instead +// make use of FileDescriptorSet which preserves the necessary information. +message Field { + // Basic field types. + enum Kind { + // Field type unknown. + TYPE_UNKNOWN = 0; + // Field type double. + TYPE_DOUBLE = 1; + // Field type float. + TYPE_FLOAT = 2; + // Field type int64. + TYPE_INT64 = 3; + // Field type uint64. + TYPE_UINT64 = 4; + // Field type int32. + TYPE_INT32 = 5; + // Field type fixed64. + TYPE_FIXED64 = 6; + // Field type fixed32. + TYPE_FIXED32 = 7; + // Field type bool. + TYPE_BOOL = 8; + // Field type string. + TYPE_STRING = 9; + // Field type group. Proto2 syntax only, and deprecated. + TYPE_GROUP = 10; + // Field type message. + TYPE_MESSAGE = 11; + // Field type bytes. + TYPE_BYTES = 12; + // Field type uint32. + TYPE_UINT32 = 13; + // Field type enum. + TYPE_ENUM = 14; + // Field type sfixed32. + TYPE_SFIXED32 = 15; + // Field type sfixed64. + TYPE_SFIXED64 = 16; + // Field type sint32. + TYPE_SINT32 = 17; + // Field type sint64. + TYPE_SINT64 = 18; + } + + // Whether a field is optional, required, or repeated. + enum Cardinality { + // For fields with unknown cardinality. + CARDINALITY_UNKNOWN = 0; + // For optional fields. + CARDINALITY_OPTIONAL = 1; + // For required fields. Proto2 syntax only. + CARDINALITY_REQUIRED = 2; + // For repeated fields. + CARDINALITY_REPEATED = 3; + } + + // The field type. + Kind kind = 1; + // The field cardinality. + Cardinality cardinality = 2; + // The field number. + int32 number = 3; + // The field name. + string name = 4; + // The field type URL, without the scheme, for message or enumeration + // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. + string type_url = 6; + // The index of the field type in `Type.oneofs`, for message or enumeration + // types. The first type has index 1; zero means the type is not in the list. + int32 oneof_index = 7; + // Whether to use alternative packed wire representation. + bool packed = 8; + // The protocol buffer options. + repeated Option options = 9; + // The field JSON name. + string json_name = 10; + // The string value of the default value of this field. Proto2 syntax only. + string default_value = 11; +} + +// Enum type definition. +// +// New usages of this message as an alternative to EnumDescriptorProto are +// strongly discouraged. This message does not reliability preserve all +// information necessary to model the schema and preserve semantics. Instead +// make use of FileDescriptorSet which preserves the necessary information. +message Enum { + // Enum type name. + string name = 1; + // Enum value definitions. + repeated EnumValue enumvalue = 2; + // Protocol buffer options. + repeated Option options = 3; + // The source context. + SourceContext source_context = 4; + // The source syntax. + Syntax syntax = 5; + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 6; +} + +// Enum value definition. +// +// New usages of this message as an alternative to EnumValueDescriptorProto are +// strongly discouraged. This message does not reliability preserve all +// information necessary to model the schema and preserve semantics. Instead +// make use of FileDescriptorSet which preserves the necessary information. +message EnumValue { + // Enum value name. + string name = 1; + // Enum value number. + int32 number = 2; + // Protocol buffer options. + repeated Option options = 3; +} + +// A protocol buffer option, which can be attached to a message, field, +// enumeration, etc. +// +// New usages of this message as an alternative to FileOptions, MessageOptions, +// FieldOptions, EnumOptions, EnumValueOptions, ServiceOptions, or MethodOptions +// are strongly discouraged. +message Option { + // The option's name. For protobuf built-in options (options defined in + // descriptor.proto), this is the short name. For example, `"map_entry"`. + // For custom options, it should be the fully-qualified name. For example, + // `"google.api.http"`. + string name = 1; + // The option's value packed in an Any message. If the value is a primitive, + // the corresponding wrapper type defined in google/protobuf/wrappers.proto + // should be used. If the value is an enum, it should be stored as an int32 + // value using the google.protobuf.Int32Value type. + Any value = 2; +} + +// The syntax in which a protocol buffer element is defined. +enum Syntax { + // Syntax `proto2`. + SYNTAX_PROTO2 = 0; + // Syntax `proto3`. + SYNTAX_PROTO3 = 1; + // Syntax `editions`. + SYNTAX_EDITIONS = 2; +} diff --git a/.tools/protoc-35.0-win64/include/google/protobuf/wrappers.proto b/.tools/protoc-35.0-win64/include/google/protobuf/wrappers.proto new file mode 100644 index 00000000..e583e7c4 --- /dev/null +++ b/.tools/protoc-35.0-win64/include/google/protobuf/wrappers.proto @@ -0,0 +1,157 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Wrappers for primitive (non-message) types. These types were needed +// for legacy reasons and are not recommended for use in new APIs. +// +// Historically these wrappers were useful to have presence on proto3 primitive +// fields, but proto3 syntax has been updated to support the `optional` keyword. +// Using that keyword is now the strongly preferred way to add presence to +// proto3 primitive fields. +// +// A secondary usecase was to embed primitives in the `google.protobuf.Any` +// type: it is now recommended that you embed your value in your own wrapper +// message which can be specifically documented. +// +// These wrappers have no meaningful use within repeated fields as they lack +// the ability to detect presence on individual elements. +// These wrappers have no meaningful use within a map or a oneof since +// individual entries of a map or fields of a oneof can already detect presence. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "WrappersProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// Wrapper message for `double`. +// +// The JSON representation for `DoubleValue` is JSON number. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message DoubleValue { + // The double value. + double value = 1; +} + +// Wrapper message for `float`. +// +// The JSON representation for `FloatValue` is JSON number. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message FloatValue { + // The float value. + float value = 1; +} + +// Wrapper message for `int64`. +// +// The JSON representation for `Int64Value` is JSON string. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message Int64Value { + // The int64 value. + int64 value = 1; +} + +// Wrapper message for `uint64`. +// +// The JSON representation for `UInt64Value` is JSON string. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message UInt64Value { + // The uint64 value. + uint64 value = 1; +} + +// Wrapper message for `int32`. +// +// The JSON representation for `Int32Value` is JSON number. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message Int32Value { + // The int32 value. + int32 value = 1; +} + +// Wrapper message for `uint32`. +// +// The JSON representation for `UInt32Value` is JSON number. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message UInt32Value { + // The uint32 value. + uint32 value = 1; +} + +// Wrapper message for `bool`. +// +// The JSON representation for `BoolValue` is JSON `true` and `false`. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message BoolValue { + // The bool value. + bool value = 1; +} + +// Wrapper message for `string`. +// +// The JSON representation for `StringValue` is JSON string. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message StringValue { + // The string value. + string value = 1; +} + +// Wrapper message for `bytes`. +// +// The JSON representation for `BytesValue` is JSON string. +// +// Not recommended for use in new APIs, but still useful for legacy APIs and +// has no plan to be removed. +message BytesValue { + // The bytes value. + bytes value = 1; +} diff --git a/.tools/protoc-35.0-win64/readme.txt b/.tools/protoc-35.0-win64/readme.txt new file mode 100644 index 00000000..f7f091fb --- /dev/null +++ b/.tools/protoc-35.0-win64/readme.txt @@ -0,0 +1,12 @@ +Protocol Buffers - Google's data interchange format +Copyright 2008 Google Inc. +https://developers.google.com/protocol-buffers/ +This package contains a precompiled binary version of the protocol buffer +compiler (protoc). This binary is intended for users who want to use Protocol +Buffers in languages other than C++ but do not want to compile protoc +themselves. To install, simply place this binary somewhere in your PATH. +If you intend to use the included well known types then don't forget to +copy the contents of the 'include' directory somewhere as well, for example +into '/usr/local/include/'. +Please refer to our official github site for more installation instructions: + https://github.com/protocolbuffers/protobuf diff --git a/HELP.md b/HELP.md new file mode 100644 index 00000000..a619d1ba --- /dev/null +++ b/HELP.md @@ -0,0 +1,495 @@ +# OctoBus Windows 本地启动适配计划 + +本文记录在当前 Windows 11 Arm64 环境下把 OctoBus 服务启动起来的适配路径。后续实施目标是:在本机生成 `octobus.exe`,启动 `octobus serve --data-dir .octobus --addr 127.0.0.1:9000`,并通过 `octobus status` 访问本地管理接口。 + +## 当前代码理解 + +- 服务入口是 Go 单体命令 `cmd/octobus/main.go`,`serve` 子命令启动本地 daemon。 +- daemon 默认监听 `127.0.0.1:9000`,也可通过 `--addr` 或 `OCTOBUS_ADDR` 覆盖。 +- 数据目录默认是当前目录下的 `.octobus`,也可通过 `--data-dir` 或 `OCTOBUS_DATA_DIR` 覆盖。 +- daemon 启动后会创建 SQLite 数据库、访问日志、服务包 artifact、runtime 目录,并通过 Node.js 子进程管理 long-running 或 on-demand service。 +- 常规 service import/start 流程依赖 `node`、`npm`、`protoc`。 + +## 当前 Windows 环境状态 + +已在当前 Windows 11 Arm64 环境完成第一阶段依赖适配: + +- `go`:已安装 Go 1.26.4,路径为 `C:\Program Files\Go\bin\go.exe`。 +- `node`:已安装 Node.js v24.18.0,路径为 `C:\Program Files\nodejs\node.exe`。 +- `npm`:已安装 npm 11.16.0,路径为 `C:\Program Files\nodejs\npm.cmd`。 +- `protoc`:`winget` 下载安装失败,已改用镜像 zip 安装到项目本地 `.tools\protoc-35.0-win64\bin\protoc.exe`,版本为 libprotoc 35.0。 + +当前终端的 `PATH` 未必包含新装工具,后续命令优先使用完整路径,或重启 VS Code 后再直接使用命令名。 + +## Windows 适配重点 + +当前 checkout 的 `Taskfile.yml` 主要调用 Bash 脚本和 Unix 命令,例如 `bash ./scripts/build-octobus.sh`、`rm -rf`、`mkdir -p`、`xargs`。在 Windows cmd/PowerShell 环境下,不能直接假设这些命令可用。 + +适配策略: + +1. 第一阶段绕过 Bash/Taskfile,直接用 Go 命令构建 Windows 可执行文件。 +2. 第二阶段补 PowerShell 脚本或 Taskfile Windows 分支,把 build、clean、smoke 流程固化。 +3. 第三阶段验证 service import、Node 子进程、protoc、npm 在 Windows 下的完整链路。 + +## 依赖准备执行记录 + +1. Go 已通过 `winget install --id GoLang.Go -e --source winget` 安装成功。 +2. Node.js LTS 已通过 `winget install --id OpenJS.NodeJS.LTS -e --source winget` 安装成功。 +3. `protoc` 使用 `winget install --id Google.Protobuf` 时因远程下载超时失败,错误为 `0x80072efd`;随后从 `https://registry.npmmirror.com/-/binary/protobuf/v35.0/protoc-35.0-win64.zip` 下载并解压到项目本地 `.tools` 目录。 +4. Go 模块下载已设置 `GOPROXY=https://goproxy.cn,direct`。 +5. npm registry 暂未调整;当前阶段只启动 daemon,不需要 npm 下载依赖。 + +## 构建执行记录 + +已绕过 Bash/Taskfile,使用 Windows 原生命令构建成功: + +```powershell +& "C:\Program Files\Go\bin\go.exe" env -w GOPROXY=https://goproxy.cn,direct +& "C:\Program Files\Go\bin\go.exe" build -trimpath -o .\bin\octobus.exe .\cmd\octobus +.\bin\octobus.exe version +``` + +构建产物:`bin\octobus.exe`。 + +版本命令输出: + +```text +version: dev +commit: unknown +date: unknown +``` + +当前构建未注入 `scripts/build-octobus.sh` 中的版本 ldflags,因此版本信息为 dev/unknown。后续如需要正式版本标识,再把该 Bash 脚本中的 ldflags 翻译为 PowerShell 构建命令。 + +## 启动验证记录 + +本地 daemon 已启动成功,当前终端仍在运行: + +```powershell +.\bin\octobus.exe serve --data-dir .\.octobus --addr 127.0.0.1:9000 +``` + +启动日志已出现: + +```text +msg=daemon_listening addr=127.0.0.1:9000 +``` + +CLI 验证通过: + +```powershell +.\bin\octobus.exe status +``` + +返回: + +```json +{ + "services": 0, + "status": "ok" +} +``` + +HTTP 管理接口验证通过: + +```powershell +Invoke-WebRequest http://127.0.0.1:9000/admin/v1/status +``` + +返回 HTTP 200,内容为 `{"services":0,"status":"ok"}`。 + +数据目录 `.octobus` 已生成,包含 `access.log`、`octobus.db`、`octobus.db-shm`、`octobus.db-wal`。 + +## Service Import 适配计划 + +启动 daemon 只是第一阶段。后续如果要跑完整 service import 和实例启动,还需要验证: + +1. `protoc` 在 Windows 下能被 `internal/packageimport` 调用并生成 descriptor。 +2. Node.js service entry 在 Windows 下可通过 `exec.Command` 正常启动。 +3. `--secret-fd 3` 在 Windows 下可能存在兼容风险,需要重点验证 `internal/supervisor` 中向子进程传 secret 的方式。如果 Windows 不支持同样的 fd 传递语义,需要改为临时 secret 文件或 Windows 兼容的 handle 传递方案。 +4. npm install、npm pack 在 Windows 路径和空格路径下均能工作。 +5. long-running 实例能通过 gRPC health check,on-demand 实例能通过 stdin/stdout protobuf wire format 调用。 + +## 后续实施顺序 + +1. 当前第一阶段已完成:依赖安装、Windows 原生构建、daemon 启动、CLI/HTTP status 验证。 +2. 下一阶段建议先运行最小 Go 单元测试:`go test ./cmd/... ./internal/...`。 +3. 再选择一个最小 example,验证 import、instance create、capset、Connect RPC/MCP 基础调用。 +4. 如果 service import 阶段失败,优先排查 `protoc` 路径、npm registry、Windows 路径分隔符和空格路径。 +5. 如果实例启动阶段失败,重点排查 `--secret-fd 3` 在 Windows 下的兼容性,以及 `internal/supervisor` 的 Node 子进程启动方式。 +6. 若需要固化 Windows 流程,再补充 PowerShell 构建/清理/启动脚本或 Taskfile Windows 分支。 + +## 项目用途分析 + +OctoBus 可以理解为一个本地运行的“能力网关”或“接口适配总线”。它不是直接帮你写业务系统,而是提供一套标准方式,把不同系统的接口包装成统一的能力,然后暴露给客户端、自动化脚本或 AI Agent 调用。 + +它解决的问题是: + +- 不同系统接口形态不一致:有的系统是 REST,有的是内部 SDK,有的是数据库查询,有的是第三方 SaaS API。 +- 不同系统鉴权方式不一致:有的需要 token,有的需要账号密码,有的需要内部证书。 +- Agent 或上层应用不应该直接理解每个系统的细节,而应该看到稳定、可描述、可发现、可调用的能力列表。 +- 接口接入后需要统一管理实例、配置、密钥、方法暴露范围、访问日志和访问令牌。 + +OctoBus 的抽象层次如下: + +1. `service`:一个被接入的系统能力包。它声明有哪些 RPC 方法、需要哪些配置和密钥、由哪个 Node.js 入口实现。 +2. `instance`:某个 service 的一个运行实例。比如同一个工单系统 wrapper 可以有测试环境实例、生产环境实例,分别配置不同 baseURL 和 token。 +3. `capset`:能力集合。它决定哪些实例和哪些方法对外暴露给某个 Agent、应用或场景。 +4. `method binding`:capset 中具体选中的方法。只有被选中的方法才会出现在 catalog、Connect RPC、MCP、OpenAPI 或 gRPC 暴露面里。 + +从运行结构看: + +```text +调用方 / Agent / 脚本 + -> OctoBus 统一入口 127.0.0.1:9000 + -> 管理 API / gRPC / Connect RPC / MCP / OpenAPI + -> capset 路由和鉴权 + -> 某个 service instance + -> Node.js service package + -> 真实业务系统接口 +``` + +所以,OctoBus 的核心价值是把“新系统的接口”变成“标准可发现、可鉴权、可审计、可被 Agent 调用的工具能力”。 + +## 新系统接口如何接入 + +假设你有一个新系统,比如工单系统、WAF、CMDB、漏洞扫描平台、销售系统、日志平台等。接入 OctoBus 的本质是写一个 service package,把这个系统的接口封装起来。 + +一个最小 service package 通常包含: + +```text +my-system-service/ + package.json + service.json + config.schema.json + secret.schema.json + proto/ + my_system.proto + bin/ + my-system.js +``` + +各文件职责: + +- `package.json`:Node 包定义,声明依赖和可执行入口。OctoBus 会根据 `bin` 找到运行入口。 +- `service.json`:OctoBus 服务描述文件,声明服务名、proto 文件、运行模式、配置 schema、密钥 schema。 +- `config.schema.json`:普通配置结构,比如 `baseUrl`、`tenantId`、`timeout`。 +- `secret.schema.json`:敏感配置结构,比如 `apiToken`、`username`、`password`。 +- `proto/*.proto`:对外暴露的方法定义,相当于把系统接口整理成标准 RPC 契约。 +- `bin/*.js`:真正调用新系统接口的适配代码。 + +### 第一步:定义要暴露哪些能力 + +不要把新系统所有接口一次性全搬进来。建议先按业务场景选 1 到 3 个高价值动作,例如: + +- 查询工单详情 +- 创建工单 +- 查询资产信息 +- 拉取漏洞列表 +- 查询日志分析结果 +- 触发扫描任务 +- 获取某个项目的 MR 列表 + +然后把这些动作设计成 proto 方法。例如一个工单系统可以这样定义: + +```proto +syntax = "proto3"; +package ticket.v1; + +service TicketService { + rpc GetTicket(GetTicketRequest) returns (Ticket); + rpc CreateTicket(CreateTicketRequest) returns (Ticket); +} + +message GetTicketRequest { + string id = 1; +} + +message CreateTicketRequest { + string title = 1; + string description = 2; + string priority = 3; +} + +message Ticket { + string id = 1; + string title = 2; + string status = 3; + string url = 4; +} +``` + +### 第二步:写 service.json + +示例: + +```json +{ + "schema": "chaitin.octobus.service.v1", + "name": "ticket-wrapper", + "displayName": "Ticket System Wrapper", + "description": "Wrap ticket system APIs as OctoBus capabilities.", + "runtime": { + "mode": "long-running" + }, + "proto": { + "roots": ["proto"], + "files": ["proto/ticket.proto"] + }, + "configSchema": "config.schema.json", + "secretSchema": "secret.schema.json" +} +``` + +一般优先使用 `long-running` 模式:实例创建后常驻一个 Node.js gRPC 进程,适合频繁调用。只有很少调用、希望每次调用才启动进程时,才考虑 `on-demand`。 + +### 第三步:定义配置和密钥 + +`config.schema.json` 示例: + +```json +{ + "type": "object", + "properties": { + "baseUrl": { "type": "string" }, + "timeoutMs": { "type": "number", "default": 10000 } + }, + "required": ["baseUrl"], + "additionalProperties": false +} +``` + +`secret.schema.json` 示例: + +```json +{ + "type": "object", + "properties": { + "apiToken": { "type": "string" } + }, + "required": ["apiToken"], + "additionalProperties": false +} +``` + +配置和密钥分开是为了安全:`config` 可以记录普通参数,`secret` 放 token、密码等敏感信息。 + +### 第四步:实现 Node.js 适配代码 + +示意代码: + +```js +#!/usr/bin/env node + +import { defineService, runServiceMain } from "@chaitin-ai/octobus-sdk"; + +const service = defineService({ + handlers: { + "ticket.v1.TicketService/GetTicket": async (ctx) => { + const { baseUrl } = ctx.config; + const { apiToken } = ctx.secret; + const { id } = ctx.request; + + const resp = await fetch(`${baseUrl}/api/tickets/${id}`, { + headers: { Authorization: `Bearer ${apiToken}` }, + }); + const data = await resp.json(); + + return { + id: String(data.id), + title: data.title, + status: data.status, + url: data.url, + }; + }, + }, +}); + +runServiceMain(service); +``` + +handler 的 key 必须和 proto 中的方法全名一致,格式是: + +```text +./ +``` + +例如: + +```text +ticket.v1.TicketService/GetTicket +``` + +### 第五步:导入 service package + +如果 service package 是本地目录: + +```powershell +.\bin\octobus.exe service import ticket .\services\ticket-wrapper +``` + +如果一个包里有多个 service root,可以用: + +```powershell +.\bin\octobus.exe service import --recursive .\services\platform-services +``` + +### 第六步:创建实例 + +创建实例时传入配置和密钥: + +```powershell +.\bin\octobus.exe instance create ticket-prod ` + --service ticket ` + --config-json '{"baseUrl":"https://ticket.example.com","timeoutMs":10000}' ` + --secret-json '{"apiToken":"dev-token"}' +``` + +`long-running` service 默认创建后会启动实例。启动成功后,OctoBus 会记录实例状态、PID、监听地址,并做 gRPC health check。 + +### 第七步:创建 capset 并暴露方法 + +创建一个面向某个 Agent 或业务场景的能力集合: + +```powershell +.\bin\octobus.exe capset create dev --name DevAgent +.\bin\octobus.exe capset add-instance dev ticket-prod +``` + +默认会把该实例当前所有方法加入 capset。更精细的方式是先不自动加入所有方法,然后手动选择: + +```powershell +.\bin\octobus.exe capset add-instance dev ticket-prod --no-all-methods +.\bin\octobus.exe capset select-method dev ticket-prod ticket.v1.TicketService/GetTicket --mcp-tool ticket_get +``` + +### 第八步:查看 catalog + +```powershell +.\bin\octobus.exe catalog dev --all --json +``` + +或: + +```powershell +Invoke-WebRequest "http://127.0.0.1:9000/admin/v1/catalog/dev?all=true" +``` + +catalog 会告诉你: + +- 暴露了哪些方法 +- 每个方法属于哪个 service/instance +- gRPC 调用方式 +- Connect RPC endpoint +- MCP tool 名称 +- 请求/响应 message 名称 +- OpenAPI 描述地址 + +## 接入后怎么调用 + +同一个新系统能力接入后,可以用三类主要方式调用:Connect RPC、MCP、gRPC。 + +### 方式一:Connect RPC,适合普通 HTTP/JSON 调用 + +Connect RPC endpoint 格式: + +```text +POST /capsets/{capset_id}/connect/{instance_id}/{full_service}/{method} +``` + +以上面的工单系统为例: + +```powershell +Invoke-RestMethod ` + -Method Post ` + -Uri "http://127.0.0.1:9000/capsets/dev/connect/ticket-prod/ticket.v1.TicketService/GetTicket" ` + -ContentType "application/json" ` + -Body '{"id":"TICKET-001"}' +``` + +如果 capset 配了 token,则加: + +```powershell +-Headers @{ Authorization = "Bearer dev-secret" } +``` + +Connect RPC 的优点是简单,普通前端、脚本、后端服务都可以用 HTTP JSON 调用。 + +### 方式二:MCP,适合 AI Agent 调用 + +MCP endpoint 格式: + +```text +POST /capsets/{capset_id}/mcp +``` + +列出工具: + +```powershell +Invoke-RestMethod ` + -Method Post ` + -Uri "http://127.0.0.1:9000/capsets/dev/mcp" ` + -ContentType "application/json" ` + -Body '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' +``` + +调用工具: + +```powershell +Invoke-RestMethod ` + -Method Post ` + -Uri "http://127.0.0.1:9000/capsets/dev/mcp" ` + -ContentType "application/json" ` + -Body '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"ticket_get","arguments":{"id":"TICKET-001"}}}' +``` + +MCP 的优点是适合接给支持 MCP 的 Agent,让 Agent 自动发现工具 schema 并按参数调用。 + +### 方式三:gRPC,适合内部服务或强类型客户端 + +调用时通过 metadata 指定 capset 和 instance: + +```powershell +grpcurl -plaintext ` + -H "x-octobus-capset: dev" ` + -H "x-octobus-instance: ticket-prod" ` + -d '{"id":"TICKET-001"}' ` + 127.0.0.1:9000 ` + ticket.v1.TicketService/GetTicket +``` + +gRPC 的优点是支持原始 RPC 语义,也支持 streaming。Connect RPC 和 MCP 主要适合 unary 方法;streaming 方法应优先走 gRPC。 + +### OpenAPI 和 Schema 查看 + +OctoBus 可以按 capset 生成 OpenAPI: + +```powershell +Invoke-WebRequest http://127.0.0.1:9000/capsets/dev/openapi.json +Invoke-WebRequest http://127.0.0.1:9000/capsets/dev/openapi.yaml +``` + +这对前端、接口调试工具和文档生成有用。 + +### 访问控制和日志 + +capset 默认没有 token 时,公开协议入口可直接访问。要加访问控制: + +```powershell +"dev-secret" | .\bin\octobus.exe capset add-token dev local --token-stdin +``` + +加 token 后: + +- Connect RPC、MCP、OpenAPI 使用 `Authorization: Bearer `。 +- gRPC 和 reflection 使用同名 metadata。 +- OctoBus 只保存 token 校验哈希,不保存明文 token。 + +访问日志位于数据目录的 `access.log`,也可以用 CLI 查看: + +```powershell +.\bin\octobus.exe logs +.\bin\octobus.exe logs --capset dev --instance ticket-prod +``` + +日志记录协议、capset、service、instance、method/tool、route、状态码、耗时、来源地址等,不记录请求体、响应体、Authorization、token、secret 或业务 metadata。 From 0b40861c83d45e4030300c22683ca12e0287fe0e Mon Sep 17 00:00:00 2001 From: "tongzhou.li" Date: Tue, 30 Jun 2026 16:58:17 +0800 Subject: [PATCH 2/3] Add Zhizhangyi MBS service adapter Signed-off-by: tongzhou.li --- .gitignore | 1 + internal/protocol/gateway.go | 41 +- internal/supervisor/supervisor.go | 37 +- services/package.json | 12 +- .../zhizhangyi__mbs/bin/zhizhangyi-mbs.js | 7 + services/zhizhangyi__mbs/config.schema.json | 27 ++ services/zhizhangyi__mbs/package.json | 20 + .../proto/zhizhangyi_mbs.proto | 351 ++++++++++++++ services/zhizhangyi__mbs/secret.schema.json | 22 + services/zhizhangyi__mbs/service.json | 69 +++ services/zhizhangyi__mbs/src/service.js | 7 + .../zhizhangyi__mbs/src/zhizhangyi-mbs.js | 263 ++++++++++ .../test_octobus_mbs_client.go | 449 ++++++++++++++++++ .../zhizhangyi__mbs/testdata/test_4apis.mjs | 123 +++++ .../testdata/test_getusers.mjs | 96 ++++ .../testdata/test_mbs_client.go | 267 +++++++++++ .../testdata/test_scenarios.mjs | 94 ++++ 17 files changed, 1876 insertions(+), 10 deletions(-) create mode 100755 services/zhizhangyi__mbs/bin/zhizhangyi-mbs.js create mode 100644 services/zhizhangyi__mbs/config.schema.json create mode 100644 services/zhizhangyi__mbs/package.json create mode 100644 services/zhizhangyi__mbs/proto/zhizhangyi_mbs.proto create mode 100644 services/zhizhangyi__mbs/secret.schema.json create mode 100644 services/zhizhangyi__mbs/service.json create mode 100644 services/zhizhangyi__mbs/src/service.js create mode 100644 services/zhizhangyi__mbs/src/zhizhangyi-mbs.js create mode 100644 services/zhizhangyi__mbs/test_octobus_mbs_client.go create mode 100644 services/zhizhangyi__mbs/testdata/test_4apis.mjs create mode 100644 services/zhizhangyi__mbs/testdata/test_getusers.mjs create mode 100644 services/zhizhangyi__mbs/testdata/test_mbs_client.go create mode 100644 services/zhizhangyi__mbs/testdata/test_scenarios.mjs diff --git a/.gitignore b/.gitignore index 90cb86f9..75f9c656 100644 --- a/.gitignore +++ b/.gitignore @@ -188,6 +188,7 @@ crash.*.log # Containers and local runtime data /data/ +/.octobus-mbs-mac/ /playground/ /qemu-compose.yml docker-compose.override.yml diff --git a/internal/protocol/gateway.go b/internal/protocol/gateway.go index 3d724a82..94bc4675 100644 --- a/internal/protocol/gateway.go +++ b/internal/protocol/gateway.go @@ -14,6 +14,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "sort" "strings" "sync" @@ -1594,24 +1595,29 @@ func (g *Gateway) invokeOnDemand(ctx context.Context, item store.ExposedMethod, if !info.Mode().IsRegular() { return nil, status.Errorf(codes.Internal, "runtime entry %q is not a regular file", item.Service.NodeEntry) } - secretFile, closeSecret, err := secretReadFile(inst.SecretJSON) + secretArg, secretFile, closeSecret, err := runtimeSecretArg(workdir, inst.SecretJSON) if err != nil { - return nil, status.Errorf(codes.Internal, "prepare secret fd: %v", err) + return nil, status.Errorf(codes.Internal, "prepare runtime secret: %v", err) } defer closeSecret() - cmd := exec.CommandContext(ctx, entry, + args := []string{ "--runtime", "invoke", "--method", item.Method.FullName, "--config", filepath.Join(workdir, "config.json"), - "--secret-fd", "3", + } + args = append(args, secretArg...) + args = append(args, "--metadata", metadataPath, "--workdir", workdir, "--service", item.Service.ID, "--instance", item.Instance.ID, ) + cmd := exec.CommandContext(ctx, entry, args...) cmd.Dir = workdir - cmd.ExtraFiles = []*os.File{secretFile} + if secretFile != nil { + cmd.ExtraFiles = []*os.File{secretFile} + } cmd.Env = append(os.Environ(), "OCTOBUS_SERVICE_ID="+item.Service.ID, "OCTOBUS_INSTANCE_ID="+item.Instance.ID, @@ -1658,6 +1664,31 @@ func secretReadFile(secret []byte) (*os.File, func(), error) { return reader, closeFn, nil } +func usesSecretFD() bool { + return runtime.GOOS != "windows" || runtime.GOARCH != "arm64" +} + +func runtimeSecretArg(workdir string, secret []byte) ([]string, *os.File, func(), error) { + if usesSecretFD() { + secretFile, closeFn, err := secretReadFile(secret) + if err != nil { + return nil, nil, nil, err + } + return []string{"--secret-fd", "3"}, secretFile, closeFn, nil + } + if len(secret) == 0 { + secret = []byte(`{}`) + } + secretPath := filepath.Join(workdir, "secret.runtime.json") + if err := os.WriteFile(secretPath, secret, 0o600); err != nil { + return nil, nil, nil, err + } + cleanup := func() { + _ = os.Remove(secretPath) + } + return []string{"--secret", secretPath}, nil, cleanup, nil +} + func (g *Gateway) validateOnDemandResponse(item store.ExposedMethod, raw []byte) error { set, err := descriptors.Load(item.Service.DescriptorPath) if err != nil { diff --git a/internal/supervisor/supervisor.go b/internal/supervisor/supervisor.go index 9128a23b..b8e24786 100644 --- a/internal/supervisor/supervisor.go +++ b/internal/supervisor/supervisor.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "sync" "time" @@ -230,15 +231,20 @@ func (s *Supervisor) startWithAttempt(ctx context.Context, instanceID string, re startErr = fmt.Errorf("runtime entry %q is not a regular file", svc.NodeEntry) return startErr } - secretFile, closeSecret, err := secretReadFile(inst.SecretJSON) + secretArg, secretFile, closeSecret, err := runtimeSecretArg(workdir, inst.SecretJSON) if err != nil { startErr = err return err } defer closeSecret() - cmd := exec.Command(entry, "--runtime", "serve", "--host", "127.0.0.1", "--port", fmt.Sprintf("%d", port), "--config", filepath.Join(workdir, "config.json"), "--secret-fd", "3", "--workdir", workdir, "--service", svc.ID, "--instance", instanceID) + args := []string{"--runtime", "serve", "--host", "127.0.0.1", "--port", fmt.Sprintf("%d", port), "--config", filepath.Join(workdir, "config.json")} + args = append(args, secretArg...) + args = append(args, "--workdir", workdir, "--service", svc.ID, "--instance", instanceID) + cmd := exec.Command(entry, args...) cmd.Dir = workdir - cmd.ExtraFiles = []*os.File{secretFile} + if secretFile != nil { + cmd.ExtraFiles = []*os.File{secretFile} + } cmd.Env = append(os.Environ(), "OCTOBUS_SERVICE_ID="+svc.ID, "OCTOBUS_INSTANCE_ID="+instanceID, @@ -523,6 +529,31 @@ func secretReadFile(secret []byte) (*os.File, func(), error) { return reader, closeFn, nil } +func usesSecretFD() bool { + return runtime.GOOS != "windows" || runtime.GOARCH != "arm64" +} + +func runtimeSecretArg(workdir string, secret []byte) ([]string, *os.File, func(), error) { + if usesSecretFD() { + secretFile, closeFn, err := secretReadFile(secret) + if err != nil { + return nil, nil, nil, err + } + return []string{"--secret-fd", "3"}, secretFile, closeFn, nil + } + if len(secret) == 0 { + secret = []byte(`{}`) + } + secretPath := filepath.Join(workdir, "secret.runtime.json") + if err := os.WriteFile(secretPath, secret, 0o600); err != nil { + return nil, nil, nil, err + } + cleanup := func() { + _ = os.Remove(secretPath) + } + return []string{"--secret", secretPath}, nil, cleanup, nil +} + func (s *Supervisor) wait(instanceID string, state *processState, stdout, stderr *os.File) { err := state.cmd.Wait() _ = stdout.Close() diff --git a/services/package.json b/services/package.json index dd6a1015..84bb98f8 100644 --- a/services/package.json +++ b/services/package.json @@ -50,7 +50,8 @@ "topsec-waf-v3-2294-20238": "bin/topsec-waf-v3-2294-20238.js", "venus-ads-v3-6": "bin/venus-ads-v3-6.js", "wangsu-label-ip": "bin/wangsu-label-ip.js", - "wd-k01": "bin/wd-k01.js" + "wd-k01": "bin/wd-k01.js", + "zhizhangyi-mbs": "zhizhangyi__mbs/bin/zhizhangyi-mbs.js" }, "files": [ "bin/alibaba-cloud-simple-application-server-firewall.js", @@ -146,7 +147,14 @@ "wd__k01", "wangsu__label-ip", "scripts", - "bin/octobus-tentacles.js" + "bin/octobus-tentacles.js", + "zhizhangyi__mbs/bin/zhizhangyi-mbs.js", + "zhizhangyi__mbs/config.schema.json", + "zhizhangyi__mbs/package.json", + "zhizhangyi__mbs/proto", + "zhizhangyi__mbs/secret.schema.json", + "zhizhangyi__mbs/service.json", + "zhizhangyi__mbs/src" ], "scripts": { "validate": "node scripts/validate-service-package.mjs", diff --git a/services/zhizhangyi__mbs/bin/zhizhangyi-mbs.js b/services/zhizhangyi__mbs/bin/zhizhangyi-mbs.js new file mode 100755 index 00000000..508272f0 --- /dev/null +++ b/services/zhizhangyi__mbs/bin/zhizhangyi-mbs.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node + +import { runServiceMain } from "@chaitin-ai/octobus-sdk"; + +import { service } from "../src/service.js"; + +runServiceMain(service); diff --git a/services/zhizhangyi__mbs/config.schema.json b/services/zhizhangyi__mbs/config.schema.json new file mode 100644 index 00000000..b79b6fa4 --- /dev/null +++ b/services/zhizhangyi__mbs/config.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "additionalProperties": true, + "properties": { + "endpoint": { + "type": "string", + "default": "https://101.37.25.245:9074", + "description": "MBS REST API base URL. Default: https://101.37.25.245:9074" + }, + "baseUrl": { + "type": "string", + "description": "Legacy alias for endpoint." + }, + "skipTlsVerify": { + "type": "boolean", + "default": true, + "description": "Skip TLS certificate verification (MBS uses self-signed certs)." + }, + "timeoutMs": { + "type": "integer", + "minimum": 1, + "default": 30000, + "description": "HTTP timeout in milliseconds." + } + } +} diff --git a/services/zhizhangyi__mbs/package.json b/services/zhizhangyi__mbs/package.json new file mode 100644 index 00000000..b9df352f --- /dev/null +++ b/services/zhizhangyi__mbs/package.json @@ -0,0 +1,20 @@ +{ + "name": "zhizhangyi-mbs", + "version": "0.0.0", + "private": true, + "type": "module", + "files": [ + "bin/zhizhangyi-mbs.js", + "config.schema.json", + "secret.schema.json", + "service.json", + "proto", + "src" + ], + "bin": { + "zhizhangyi-mbs": "bin/zhizhangyi-mbs.js" + }, + "dependencies": { + "@chaitin-ai/octobus-sdk": "^0.5.0" + } +} diff --git a/services/zhizhangyi__mbs/proto/zhizhangyi_mbs.proto b/services/zhizhangyi__mbs/proto/zhizhangyi_mbs.proto new file mode 100644 index 00000000..3b6a099b --- /dev/null +++ b/services/zhizhangyi__mbs/proto/zhizhangyi_mbs.proto @@ -0,0 +1,351 @@ +syntax = "proto3"; + +package zhizhangyi.mbs; + +// ============================================================================= +// MBS (Mobile Security Management Platform) User Management API +// Vendor: 北京指掌易科技有限公司 +// Base URL: https://{host}:9074/uusafe/mos/thirdaccess/rest/opt/{version}/{operator} +// Auth: MD5(appkey + param1 + param2 + ... + secretkey), 拼接不含 "+" +// ============================================================================= + +service UserManagement { + // 6.2.1 用户列表(分页/筛选) + rpc GetUsers(GetUsersRequest) returns (GetUsersResponse); + + // 6.2.2 新增用户 + rpc AddUser(AddUserRequest) returns (AddUserResponse); + + // 6.2.3 编辑用户 + rpc UpdUser(UpdUserRequest) returns (UpdUserResponse); + + // 6.2.4 用户详情 + rpc DetailUser(DetailUserRequest) returns (DetailUserResponse); + + // 6.2.5 删除用户 + rpc DelUsers(DelUsersRequest) returns (DelUsersResponse); + + // 6.2.6 启停用户 + rpc StateUsers(StateUsersRequest) returns (StateUsersResponse); + + // 6.2.7 账号校验 + rpc CheckLoginName(CheckLoginNameRequest) returns (CheckLoginNameResponse); + + // 6.2.8 根据手机号获取用户 + rpc GetUserByPhone(GetUserByPhoneRequest) returns (GetUserByPhoneResponse); + + // 6.2.9 / 6.2.10 修改密码 + rpc UpdUserPwd(UpdUserPwdRequest) returns (UpdUserPwdResponse); + + // 6.2.11 强制下线 + rpc ForceOffline(ForceOfflineRequest) returns (ForceOfflineResponse); + + // 6.2.12 导入用户 + rpc ImportUser(ImportUserRequest) returns (ImportUserResponse); +} + +// ============================================================================= +// Common / Shared Messages +// ============================================================================= + +// 扩展字段键值对 +message ExAttrVo { + string attr_key = 1; + string attr_value = 2; +} + +// 查询条件 map +message GetUsersCondition { + string key_word = 1; // 关键字(用户名/姓名/手机号/工号/邮箱) + string state = 2; // 激活状态 -1未激活 0停用 1激活(逗号分隔多选) + string is_mdm = 3; // 是否开启设备管控 0未开启 1开启(逗号分隔多选) + string dept_id = 4; // 部门ID +} + +// 删除/启停条件 map +message BatchCondition { + string key_word = 1; + string status = 2; + string is_mdm = 3; + string dept_id = 4; +} + +// 用户基本信息(列表项) +message UserInfo { + string user_id = 1; + string user_name = 2; + string login_name = 3; + string phone_number = 4; + string email = 5; + string employee_number = 6; + string dept_id = 7; + string dept_name = 8; + int32 device_count = 9; + int32 is_mdm = 10; + int32 state = 11; + int32 is_admin = 12; + int32 user_source = 13; + int32 status = 14; + int32 weight = 15; + repeated ExAttrVo attrs = 16; + string dept_full_id = 17; + string dept_full_path = 18; + string job = 19; + string mobile = 20; + string address = 21; + string organization = 22; +} + +// 用户详情 +message UserDetail { + string user_id = 1; + string user_name = 2; + string login_name = 3; + string dept_id = 4; + string dept_name = 5; + string phone_number = 6; + string job = 7; + string employee_number = 8; + string address = 9; + string mobile = 10; + string email = 11; + string organization = 12; + int32 is_mdm = 13; + int32 state = 14; + int32 weight = 15; + string icon_file_id = 16; + repeated ExAttrVo attrs = 17; +} + +// ============================================================================= +// 6.2.1 GetUsers - 用户列表 +// ============================================================================= +message GetUsersRequest { + int32 index = 1; // 页号(0起始) + int32 size = 2; // 每页数量(默认10,最大5000) + int32 order_code = 3; // 排序字段 0用户名 1姓名 2工号 3用户状态 + int32 order_type = 4; // 排序类型 0 asc 1 desc + GetUsersCondition condition = 5; // 查询条件 + string org_code = 6; // 机构编码 + string appkey = 7; // 接入账号 + string sign = 8; // MD5签名(自动计算) +} + +message GetUsersResponse { + int32 code = 1; + string msg = 2; + GetUsersData data = 3; + string time_stamp = 4; +} + +message GetUsersData { + int32 total = 1; + repeated UserInfo user_infos = 2; +} + +// ============================================================================= +// 6.2.2 AddUser - 新增用户 +// ============================================================================= +message AddUserRequest { + string user_name = 1; // 用户姓名(必填) + string login_name = 2; // 登录名(必填) + string dept_id = 3; // 部门ID(必填) + string password = 4; // 密码 3DES加密(必填) + string phone_number = 5; + string job = 6; + string employee_number = 7; + string address = 8; + string mobile = 9; + string email = 10; + string organization = 11; + int32 user_source = 12; // 0本地 2第三方(必填) + int32 is_mdm = 13; // 是否开启设备管理 0否 1是 + int32 state = 14; // 1启用 0停用 -1未激活 + int32 weight = 15; + repeated ExAttrVo attrs = 16; + string org_code = 17; + string appkey = 18; + string sign = 19; +} + +message AddUserResponse { + int32 code = 1; + string msg = 2; + string data = 3; // 通常为空对象 +} + +// ============================================================================= +// 6.2.3 UpdUser - 编辑用户 +// ============================================================================= +message UpdUserRequest { + string user_id = 1; // 用户ID(必填) + string user_name = 2; // 用户姓名(必填) + string login_name = 3; // 登录名(不可修改账号) + string dept_id = 4; // 部门ID(必填) + string phone_number = 5; + string job = 6; + string employee_number = 7; + string address = 8; + string mobile = 9; + string email = 10; + string organization = 11; + int32 is_mdm = 12; + int32 weight = 13; + repeated ExAttrVo attrs = 14; + string org_code = 15; + string appkey = 16; + string sign = 17; +} + +message UpdUserResponse { + int32 code = 1; + string msg = 2; + string data = 3; +} + +// ============================================================================= +// 6.2.4 DetailUser - 用户详情 +// ============================================================================= +message DetailUserRequest { + string user_id = 1; + string org_code = 2; + string appkey = 3; + string sign = 4; +} + +message DetailUserResponse { + int32 code = 1; + string msg = 2; + UserDetail data = 3; +} + +// ============================================================================= +// 6.2.5 DelUsers - 删除用户 +// ============================================================================= +message DelUsersRequest { + repeated string user_ids = 1; // 用户ID列表 + int32 type = 2; // 0指定ID删除 1指定条件删除 + BatchCondition condition = 3; // type=1时的查询条件 + string org_code = 4; + string appkey = 5; + string sign = 6; +} + +message DelUsersResponse { + int32 code = 1; + string msg = 2; + string data = 3; +} + +// ============================================================================= +// 6.2.6 StateUsers - 启停用户 +// ============================================================================= +message StateUsersRequest { + repeated string user_ids = 1; + int32 type = 2; // 0指定ID 1指定条件 + string state = 3; // 0停用 1启用 + BatchCondition condition = 4; + string org_code = 5; + string appkey = 6; + string sign = 7; +} + +message StateUsersResponse { + int32 code = 1; + string msg = 2; + string data = 3; +} + +// ============================================================================= +// 6.2.7 CheckLoginName - 账号校验 +// ============================================================================= +message CheckLoginNameRequest { + string login_name = 1; + string org_code = 2; + string appkey = 3; + string sign = 4; +} + +message CheckLoginNameResponse { + int32 code = 1; + string msg = 2; + string data = 3; +} + +// ============================================================================= +// 6.2.8 GetUserByPhone - 根据手机号获取用户 +// ============================================================================= +message GetUserByPhoneRequest { + string phone = 1; + string org_code = 2; + string appkey = 3; + string sign = 4; +} + +message GetUserByPhoneResponse { + int32 code = 1; + string msg = 2; + repeated PhoneUserInfo data = 3; +} + +message PhoneUserInfo { + string user_id = 1; + string user_name = 2; + string login_name = 3; + string phone_number = 4; + string email = 5; +} + +// ============================================================================= +// 6.2.9 / 6.2.10 UpdUserPwd - 修改密码 +// ============================================================================= +message UpdUserPwdRequest { + string user_id = 1; // v1版本必填 + string password = 2; // 新密码 3DES加密(v1) + string old_pwd = 3; // 原密码 3DES加密(v2) + string new_pwd = 4; // 新密码 3DES加密(v2) + string login_name = 5; // v2版本必填 + string version = 6; // "v1" 管理员修改 / "v2" 用户自服务 + string org_code = 7; + string appkey = 8; + string sign = 9; +} + +message UpdUserPwdResponse { + int32 code = 1; + string msg = 2; + string data = 3; +} + +// ============================================================================= +// 6.2.11 ForceOffline - 强制下线 +// ============================================================================= +message ForceOfflineRequest { + string user_id = 1; + string org_code = 2; + string appkey = 3; + string sign = 4; +} + +message ForceOfflineResponse { + int32 code = 1; + string msg = 2; + string data = 3; +} + +// ============================================================================= +// 6.2.12 ImportUser - 导入用户 +// ============================================================================= +message ImportUserRequest { + int32 lang = 1; // 0中文 1英文 2简体中文 + string file_id = 2; // 文件ID + string org_code = 3; + string appkey = 4; + string sign = 5; +} + +message ImportUserResponse { + int32 code = 1; + string msg = 2; + string data = 3; +} \ No newline at end of file diff --git a/services/zhizhangyi__mbs/secret.schema.json b/services/zhizhangyi__mbs/secret.schema.json new file mode 100644 index 00000000..7513b1da --- /dev/null +++ b/services/zhizhangyi__mbs/secret.schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "additionalProperties": true, + "properties": { + "appkey": { + "type": "string", + "default": "jiekoudiaoyongappkey", + "description": "MBS third-party access appkey. Default: jiekoudiaoyongappkey" + }, + "secretkey": { + "type": "string", + "default": "jiekoudiaoyongsecretkey", + "description": "MBS third-party access secretkey paired with appkey." + }, + "orgCode": { + "type": "string", + "default": "test", + "description": "Default organization code (orgCode) for MBS tenant. Default: test" + } + } +} diff --git a/services/zhizhangyi__mbs/service.json b/services/zhizhangyi__mbs/service.json new file mode 100644 index 00000000..9fb14081 --- /dev/null +++ b/services/zhizhangyi__mbs/service.json @@ -0,0 +1,69 @@ +{ + "schema": "chaitin.octobus.service.v1", + "name": "zhizhangyi-mbs", + "displayName": "MBS Mobile Security Management", + "description": "OctoBus package for 指掌易 MBS (Mobile Security) User Management APIs. Provides CRUD for users, password management, account validation, force offline, and bulk import.", + "runtime": { + "mode": "long-running" + }, + "proto": { + "roots": [ + "proto" + ], + "files": [ + "proto/zhizhangyi_mbs.proto" + ] + }, + "configSchema": "config.schema.json", + "secretSchema": "secret.schema.json", + "sdk": { + "cli": { + "commands": { + "zhizhangyi.mbs.UserManagement/GetUsers": { + "name": "get-users", + "description": "List MBS users with pagination, filtering, and sorting." + }, + "zhizhangyi.mbs.UserManagement/AddUser": { + "name": "add-user", + "description": "Create a new MBS user." + }, + "zhizhangyi.mbs.UserManagement/UpdUser": { + "name": "update-user", + "description": "Update an existing MBS user." + }, + "zhizhangyi.mbs.UserManagement/DetailUser": { + "name": "detail-user", + "description": "Get MBS user detail by user ID." + }, + "zhizhangyi.mbs.UserManagement/DelUsers": { + "name": "delete-users", + "description": "Delete MBS users by ID or condition." + }, + "zhizhangyi.mbs.UserManagement/StateUsers": { + "name": "state-users", + "description": "Enable or disable MBS users." + }, + "zhizhangyi.mbs.UserManagement/CheckLoginName": { + "name": "check-login-name", + "description": "Check if a login name is available." + }, + "zhizhangyi.mbs.UserManagement/GetUserByPhone": { + "name": "get-user-by-phone", + "description": "Find MBS users by phone number." + }, + "zhizhangyi.mbs.UserManagement/UpdUserPwd": { + "name": "update-user-password", + "description": "Change user password (admin or self-service)." + }, + "zhizhangyi.mbs.UserManagement/ForceOffline": { + "name": "force-offline", + "description": "Force a user to be logged out." + }, + "zhizhangyi.mbs.UserManagement/ImportUser": { + "name": "import-user", + "description": "Import users from an uploaded file." + } + } + } + } +} diff --git a/services/zhizhangyi__mbs/src/service.js b/services/zhizhangyi__mbs/src/service.js new file mode 100644 index 00000000..d69d1c52 --- /dev/null +++ b/services/zhizhangyi__mbs/src/service.js @@ -0,0 +1,7 @@ +import { defineService } from "@chaitin-ai/octobus-sdk"; + +import { handlers } from "./zhizhangyi-mbs.js"; + +export { handlers } from "./zhizhangyi-mbs.js"; + +export const service = defineService({ handlers }); diff --git a/services/zhizhangyi__mbs/src/zhizhangyi-mbs.js b/services/zhizhangyi__mbs/src/zhizhangyi-mbs.js new file mode 100644 index 00000000..fe707402 --- /dev/null +++ b/services/zhizhangyi__mbs/src/zhizhangyi-mbs.js @@ -0,0 +1,263 @@ +// MBS User Management API Proxy - Part 1: helpers + first 6 handlers +import { GrpcError, grpcStatus } from '@chaitin-ai/octobus-sdk'; +import crypto from 'node:crypto'; + +const DEF_TO = 30000; +const BASE = '/uusafe/mos/thirdaccess/rest/opt'; + +const has = (o, k) => Object.prototype.hasOwnProperty.call(o ?? {}, k); +const first = (...vs) => vs.find((v) => v !== undefined && v !== null); +const merge = (ctx = {}) => ({ ...(ctx?.config ?? {}), ...(ctx?.secret ?? {}), ...(ctx?.bindings ?? {}) }); +const normUrl = (u) => /^https?:\/\//i.test(String(u || '').trim()) ? String(u).trim().replace(/\/$/, '') : null; +const md5 = (s) => crypto.createHash('md5').update(s, 'utf8').digest('hex'); +const signCalc = (sk, ...ps) => md5(ps.map((p) => (p === undefined || p === null ? '' : String(p))).join('') + String(sk || '')); +const arr = (v) => Array.isArray(v) ? v : []; +const str = (v) => (v === undefined || v === null || v === '') ? undefined : String(v); +const num = (v) => (v === undefined || v === null) ? undefined : Number(v); +const gc = (c) => ({ INVALID_ARGUMENT: grpcStatus.INVALID_ARGUMENT, FAILED_PRECONDITION: grpcStatus.FAILED_PRECONDITION, PERMISSION_DENIED: grpcStatus.PERMISSION_DENIED, UNAVAILABLE: grpcStatus.UNAVAILABLE, DEADLINE_EXCEEDED: grpcStatus.DEADLINE_EXCEEDED })[c] ?? grpcStatus.UNKNOWN; +const er = (c, m) => { const e = new GrpcError(gc(c), c + ': ' + m); e.legacyCode = c; return e; }; +const doFetch = async (url, init, to, st) => { const tls = st ? { insecureSkipVerify: true, tlsInsecureSkipVerify: true } : {}; try { return await fetch(url, { ...init, timeoutMs: to, ...tls }); } catch (e) { throw er('UNAVAILABLE', e?.cause?.message || e?.message || 'fetch failed'); } }; +const rdJson = async (res) => { const t = await res.text(); if (!res.ok) throw er(res.status === 401 || res.status === 403 ? 'PERMISSION_DENIED' : res.status >= 400 && res.status < 500 ? 'FAILED_PRECONDITION' : 'UNAVAILABLE', 'http ' + res.status + ': ' + t); if (!t.trim()) return {}; try { return JSON.parse(t); } catch { throw er('UNKNOWN', 'not JSON'); } }; +const check = (j) => { if (j.code !== undefined && j.code !== 0) throw er('FAILED_PRECONDITION', 'MBS code=' + j.code + ': ' + (j.msg || '')); }; +const buildCond = (cond) => { const c = {}; if (cond?.key_word || cond?.keyWord) c.keyWord = cond?.key_word || cond?.keyWord; if (cond?.status) c.status = cond.status; if (cond?.is_mdm || cond?.isMdm) c.isMdm = cond?.is_mdm || cond?.isMdm; if (cond?.dept_id || cond?.deptId) c.deptId = cond?.dept_id || cond?.deptId; return c; }; + +const mapUser = (it) => ({ user_id: it?.userId ?? '', user_name: it?.userName ?? '', login_name: it?.loginName ?? '', phone_number: it?.phoneNumber ?? '', email: it?.email ?? '', employee_number: it?.employeeNumber ?? '', dept_id: it?.deptId ?? '', dept_name: it?.deptName ?? '', device_count: it?.deviceCount ?? 0, is_mdm: it?.isMdm ?? 0, state: it?.state ?? 0, is_admin: it?.isAdmin ?? 0, user_source: it?.userSource ?? 0, status: it?.status ?? 1, weight: it?.weight ?? 0, attrs: arr(it?.attrs).map((a) => ({ attr_key: a?.attrKey ?? '', attr_value: a?.attrValue ?? '' })), dept_full_id: it?.deptFullId ?? '', dept_full_path: it?.deptFullPath ?? '', job: it?.job ?? '', mobile: it?.mobile ?? '', address: it?.address ?? '', organization: it?.organization ?? '' }); +const mapDetail = (it) => ({ user_id: it?.userId ?? '', user_name: it?.userName ?? '', login_name: it?.loginName ?? '', dept_id: it?.deptId ?? '', dept_name: it?.deptName ?? '', phone_number: it?.phoneNumber ?? '', job: it?.job ?? '', employee_number: it?.employeeNumber ?? '', address: it?.address ?? '', mobile: it?.mobile ?? '', email: it?.email ?? '', organization: it?.organization ?? '', is_mdm: it?.isMdm ?? 0, state: it?.state ?? 0, weight: it?.weight ?? 0, icon_file_id: it?.iconFileId ?? '', attrs: arr(it?.attrs).map((a) => ({ attr_key: a?.attrKey ?? '', attr_value: a?.attrValue ?? '' })) }); +const mapPhone = (it) => ({ user_id: it?.userId ?? '', user_name: it?.userName ?? '', login_name: it?.loginName ?? '', phone_number: it?.phoneNumber ?? '', email: it?.email ?? '' }); + +const M = { + GetUsers: 'zhizhangyi.mbs.UserManagement/GetUsers', + AddUser: 'zhizhangyi.mbs.UserManagement/AddUser', + UpdUser: 'zhizhangyi.mbs.UserManagement/UpdUser', + DetailUser: 'zhizhangyi.mbs.UserManagement/DetailUser', + DelUsers: 'zhizhangyi.mbs.UserManagement/DelUsers', + StateUsers: 'zhizhangyi.mbs.UserManagement/StateUsers', + CheckLoginName: 'zhizhangyi.mbs.UserManagement/CheckLoginName', + GetUserByPhone: 'zhizhangyi.mbs.UserManagement/GetUserByPhone', + UpdUserPwd: 'zhizhangyi.mbs.UserManagement/UpdUserPwd', + ForceOffline: 'zhizhangyi.mbs.UserManagement/ForceOffline', + ImportUser: 'zhizhangyi.mbs.UserManagement/ImportUser', +}; + +export function rpcdef(ctx) { + const b = merge(ctx); + const baseUrl = normUrl(b.endpoint || b.baseUrl || ''); + const to = ctx?.limits?.timeoutMs || b.timeoutMs || DEF_TO; + const skipTls = Boolean(b.skipTlsVerify); + const ak = b.appkey || ''; + const sk = b.secretkey || ''; + const ocDef = b.orgCode || ''; + + const post = async (path, body) => { + if (!baseUrl) throw er('INVALID_ARGUMENT', 'endpoint/baseUrl required'); + const r = await doFetch(baseUrl + path, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(body) }, to, skipTls); + const j = await rdJson(r); check(j); return j; + }; + + // 6.2.1 GetUsers + const goGetUsers = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; + const apk = first(req?.appkey) || ak; + const idx = first(req?.index) ?? 0; const sz = first(req?.size) ?? 10; + const odc = first(req?.order_code, req?.orderCode) ?? 0; const odt = first(req?.order_type, req?.orderType) ?? 1; + const did = first(req?.condition?.dept_id, req?.condition?.deptId) || '1'; + const kw = first(req?.condition?.key_word, req?.condition?.keyWord) || ''; + const st = first(req?.condition?.state) || ''; const md = first(req?.condition?.is_mdm, req?.condition?.isMdm) || ''; + const sg = first(req?.sign) || signCalc(sk, apk, oc, idx, sz, odc, odt, kw, st, md, did); + const cond = { deptId: did, keyWord: kw }; if (st) cond.state = st; if (md) cond.isMdm = md; + const j = await post(BASE + '/v1/getUsers', { index: Number(idx), size: Number(sz), orderCode: Number(odc), orderType: Number(odt), condition: cond, orgCode: oc, appkey: apk, sign: sg }); + const d = j?.data || {}; + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: { total: d.total ?? 0, user_infos: arr(d.userInfos).map(mapUser) }, time_stamp: j?.timeStamp ?? '' }; + }; + + // 6.2.2 AddUser + const goAddUser = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const un = first(req?.user_name, req?.userName) || ''; const ln = first(req?.login_name, req?.loginName) || ''; + const did = first(req?.dept_id, req?.deptId) || ''; const pw = first(req?.password) || ''; + if (!un) throw er('INVALID_ARGUMENT', 'user_name required'); if (!ln) throw er('INVALID_ARGUMENT', 'login_name required'); if (!did) throw er('INVALID_ARGUMENT', 'dept_id required'); + const sg = first(req?.sign) || signCalc(sk, apk, oc, un, ln, did, pw); + const body = { userName: un, loginName: ln, deptId: did, password: pw, userSource: Number(first(req?.user_source, req?.userSource) ?? 0), orgCode: oc, appkey: apk, sign: sg }; + const sm = { phone_number: 'phoneNumber', job: 'job', employee_number: 'employeeNumber', address: 'address', mobile: 'mobile', email: 'email', organization: 'organization' }; + for (const [k, jk] of Object.entries(sm)) { const v = str(first(req?.[k])); if (v !== undefined) body[jk] = v; } + for (const k of ['is_mdm', 'state', 'weight']) { const v = num(first(req?.[k])); if (v !== undefined) body[k === 'is_mdm' ? 'isMdm' : k] = v; } + if (arr(req?.attrs).length > 0) body.attrs = req.attrs.map((a) => ({ attrKey: a?.attr_key ?? '', attrValue: a?.attr_value ?? '' })); + const j = await post(BASE + '/v1/addUser', body); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ?? null }; + }; + + // 6.2.3 UpdUser + const goUpdUser = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const uid = first(req?.user_id, req?.userId) || ''; const un = first(req?.user_name, req?.userName) || ''; + if (!uid) throw er('INVALID_ARGUMENT', 'user_id required'); if (!un) throw er('INVALID_ARGUMENT', 'user_name required'); + const ln = first(req?.login_name, req?.loginName) || ''; const did = first(req?.dept_id, req?.deptId) || ''; + const sg = first(req?.sign) || signCalc(sk, apk, oc, uid, un, ln, did); + const body = { userId: uid, userName: un, loginName: ln, deptId: did, orgCode: oc, appkey: apk, sign: sg }; + const sm = { phone_number: 'phoneNumber', job: 'job', employee_number: 'employeeNumber', address: 'address', mobile: 'mobile', email: 'email', organization: 'organization' }; + for (const [k, jk] of Object.entries(sm)) { const v = str(first(req?.[k])); if (v !== undefined) body[jk] = v; } + for (const k of ['is_mdm', 'weight']) { const v = num(first(req?.[k])); if (v !== undefined) body[k === 'is_mdm' ? 'isMdm' : k] = v; } + if (arr(req?.attrs).length > 0) body.attrs = req.attrs.map((a) => ({ attrKey: a?.attr_key ?? '', attrValue: a?.attr_value ?? '' })); + const j = await post(BASE + '/v1/updUser', body); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ?? null }; + }; + + // 6.2.4 DetailUser + const goDetailUser = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const uid = first(req?.user_id, req?.userId) || ''; if (!uid) throw er('INVALID_ARGUMENT', 'user_id required'); + const sg = first(req?.sign) || signCalc(sk, apk, oc, uid); + const j = await post(BASE + '/v1/detailUser', { userId: uid, orgCode: oc, appkey: apk, sign: sg }); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ? mapDetail(j.data) : null }; + }; + + // 6.2.5 DelUsers + const goDelUsers = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const tp = first(req?.type) ?? 0; const uids = arr(first(req?.user_ids, req?.userIds)); + const did = first(req?.condition?.dept_id, req?.condition?.deptId) || ''; + const sg = first(req?.sign) || signCalc(sk, apk, oc, uids.join(','), tp, did); + const body = { type: Number(tp), orgCode: oc, appkey: apk, sign: sg }; + if (tp === 0) { body.userIds = uids; } else { const c = buildCond(req?.condition); if (Object.keys(c).length > 0) body.condition = c; } + const j = await post(BASE + '/v1/delUsers', body); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ?? null }; + }; + + // 6.2.6 StateUsers + const goStateUsers = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const tp = first(req?.type) ?? 0; const st = first(req?.state) || '0'; + const uids = arr(first(req?.user_ids, req?.userIds)); + const did = first(req?.condition?.dept_id, req?.condition?.deptId) || ''; + const sg = first(req?.sign) || signCalc(sk, apk, oc, uids.join(','), tp, st, did); + const body = { type: Number(tp), state: st, orgCode: oc, appkey: apk, sign: sg }; + if (tp === 0) { body.userIds = uids; } else { const c = buildCond(req?.condition); if (Object.keys(c).length > 0) body.condition = c; } + const j = await post(BASE + '/v1/stateUsers', body); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ?? null }; + }; + + // 6.2.7 CheckLoginName + const goCheckLoginName = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const ln = first(req?.login_name, req?.loginName) || ''; if (!ln) throw er('INVALID_ARGUMENT', 'login_name required'); + const sg = first(req?.sign) || signCalc(sk, apk, oc, ln); + const j = await post(BASE + '/v1/checkLoginName', { loginName: ln, orgCode: oc, appkey: apk, sign: sg }); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ?? null }; + }; + + // 6.2.8 GetUserByPhone + const goGetUserByPhone = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const ph = first(req?.phone) || ''; if (!ph) throw er('INVALID_ARGUMENT', 'phone required'); + const sg = first(req?.sign) || signCalc(sk, apk, oc, ph); + const j = await post(BASE + '/v1/getUserByPhone', { phone: ph, orgCode: oc, appkey: apk, sign: sg }); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: arr(j?.data).map(mapPhone) }; + }; + + // 6.2.9/6.2.10 UpdUserPwd + const goUpdUserPwd = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const ver = first(req?.version) || 'v1'; + let body; + if (ver === 'v2') { + const ln = first(req?.login_name, req?.loginName) || ''; const np = first(req?.new_pwd, req?.newPwd) || ''; + if (!ln) throw er('INVALID_ARGUMENT', 'login_name required for v2'); if (!np) throw er('INVALID_ARGUMENT', 'new_pwd required for v2'); + const sg = first(req?.sign) || signCalc(sk, apk, oc, ln, np); + body = { loginName: ln, newPwd: np, orgCode: oc, appkey: apk, sign: sg }; + const op = first(req?.old_pwd, req?.oldPwd); if (op) body.oldPwd = op; + } else { + const uid = first(req?.user_id, req?.userId) || ''; const pw = first(req?.password) || ''; + if (!uid) throw er('INVALID_ARGUMENT', 'user_id required for v1'); if (!pw) throw er('INVALID_ARGUMENT', 'password required for v1'); + const sg = first(req?.sign) || signCalc(sk, apk, oc, uid, pw); + body = { userId: uid, password: pw, orgCode: oc, appkey: apk, sign: sg }; + } + const j = await post(BASE + `/${ver}/updUserPwd`, body); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ?? null }; + }; + + // 6.2.11 ForceOffline + const goForceOffline = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const uid = first(req?.user_id, req?.userId) || ''; if (!uid) throw er('INVALID_ARGUMENT', 'user_id required'); + const sg = first(req?.sign) || signCalc(sk, apk, oc, uid); + const j = await post(BASE + '/v1/forceOffline', { userId: uid, orgCode: oc, appkey: apk, sign: sg }); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ?? null }; + }; + + // 6.2.12 ImportUser + const goImportUser = async (req) => { + const oc = first(req?.org_code, req?.orgCode) || ocDef; const apk = first(req?.appkey) || ak; + const lang = first(req?.lang) ?? 0; const fid = first(req?.file_id, req?.fileId) || ''; + if (!fid) throw er('INVALID_ARGUMENT', 'file_id required'); + const sg = first(req?.sign) || signCalc(sk, apk, oc, lang, fid); + const j = await post(BASE + '/v1/importUser', { lang: Number(lang), fileId: fid, orgCode: oc, appkey: apk, sign: sg }); + return { code: j?.code ?? 0, msg: j?.msg ?? '', data: j?.data ?? null }; + }; + + return { + [M.GetUsers]: async () => goGetUsers(ctx.req || {}), + [M.AddUser]: async () => goAddUser(ctx.req || {}), + [M.UpdUser]: async () => goUpdUser(ctx.req || {}), + [M.DetailUser]: async () => goDetailUser(ctx.req || {}), + [M.DelUsers]: async () => goDelUsers(ctx.req || {}), + [M.StateUsers]: async () => goStateUsers(ctx.req || {}), + [M.CheckLoginName]: async () => goCheckLoginName(ctx.req || {}), + [M.GetUserByPhone]: async () => goGetUserByPhone(ctx.req || {}), + [M.UpdUserPwd]: async () => goUpdUserPwd(ctx.req || {}), + [M.ForceOffline]: async () => goForceOffline(ctx.req || {}), + [M.ImportUser]: async () => goImportUser(ctx.req || {}), + }; +} + + +// Legacy handler wrapper +function wrapLegacyHandler(baseCtx, methodPath) { + return async function(reqOrCtx, maybeInnerCtx) { + var incoming = (reqOrCtx && typeof reqOrCtx === 'object') ? reqOrCtx : {}; + var callCtx = { + ...(baseCtx ?? {}), + ...incoming, + req: incoming.request ?? incoming.req ?? reqOrCtx ?? {}, + request: incoming.request ?? incoming.req ?? reqOrCtx ?? {}, + config: incoming.config ?? baseCtx?.config, + secret: incoming.secret ?? baseCtx?.secret, + metadata: incoming.metadata ?? baseCtx?.metadata, + meta: incoming.meta ?? baseCtx?.meta, + getMetadata: incoming.getMetadata ?? baseCtx?.getMetadata, + getMetadataAll: incoming.getMetadataAll ?? baseCtx?.getMetadataAll, + }; + return rpcdef(callCtx)[methodPath](); + }; +} + +function registerHandlers(ctx) { + return { + [M.GetUsers]: wrapLegacyHandler(ctx, M.GetUsers), + [M.AddUser]: wrapLegacyHandler(ctx, M.AddUser), + [M.UpdUser]: wrapLegacyHandler(ctx, M.UpdUser), + [M.DetailUser]: wrapLegacyHandler(ctx, M.DetailUser), + [M.DelUsers]: wrapLegacyHandler(ctx, M.DelUsers), + [M.StateUsers]: wrapLegacyHandler(ctx, M.StateUsers), + [M.CheckLoginName]: wrapLegacyHandler(ctx, M.CheckLoginName), + [M.GetUserByPhone]: wrapLegacyHandler(ctx, M.GetUserByPhone), + [M.UpdUserPwd]: wrapLegacyHandler(ctx, M.UpdUserPwd), + [M.ForceOffline]: wrapLegacyHandler(ctx, M.ForceOffline), + [M.ImportUser]: wrapLegacyHandler(ctx, M.ImportUser), + }; +} + +var sdkHandlers = registerHandlers({}); + +export var handlers = { + [M.GetUsers]: (ctx) => sdkHandlers[M.GetUsers](ctx), + [M.AddUser]: (ctx) => sdkHandlers[M.AddUser](ctx), + [M.UpdUser]: (ctx) => sdkHandlers[M.UpdUser](ctx), + [M.DetailUser]: (ctx) => sdkHandlers[M.DetailUser](ctx), + [M.DelUsers]: (ctx) => sdkHandlers[M.DelUsers](ctx), + [M.StateUsers]: (ctx) => sdkHandlers[M.StateUsers](ctx), + [M.CheckLoginName]: (ctx) => sdkHandlers[M.CheckLoginName](ctx), + [M.GetUserByPhone]: (ctx) => sdkHandlers[M.GetUserByPhone](ctx), + [M.UpdUserPwd]: (ctx) => sdkHandlers[M.UpdUserPwd](ctx), + [M.ForceOffline]: (ctx) => sdkHandlers[M.ForceOffline](ctx), + [M.ImportUser]: (ctx) => sdkHandlers[M.ImportUser](ctx), +}; diff --git a/services/zhizhangyi__mbs/test_octobus_mbs_client.go b/services/zhizhangyi__mbs/test_octobus_mbs_client.go new file mode 100644 index 00000000..56504cc7 --- /dev/null +++ b/services/zhizhangyi__mbs/test_octobus_mbs_client.go @@ -0,0 +1,449 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" +) + +type clientConfig struct { + BaseURL string + Capset string + Instance string + Token string +} + +var cfg = clientConfig{ + BaseURL: "http://127.0.0.1:9000", + Capset: "mbs-scenarios", + Instance: "mbs-test", + Token: "", +} + +const ( + uid1 = "1691979294102310912" // test1 + uid2 = "1691979421122613248" // test2 +) + +var pass, failn int + +func gf(m map[string]any, k string) float64 { + f, _ := m[k].(float64) + return f +} + +func gs(m map[string]any, k string) string { + s, _ := m[k].(string) + return s +} + +func gm(m map[string]any, k string) map[string]any { + v, _ := m[k].(map[string]any) + return v +} + +func ok(s string, a ...any) { + pass++ + fmt.Printf(" PASS | "+s+"\n", a...) +} + +func fl(s string, a ...any) { + failn++ + fmt.Printf(" FAIL | "+s+"\n", a...) +} + +func inf(s string, a ...any) { + fmt.Printf(" INFO | "+s+"\n", a...) +} + +func scene(note string) { + fmt.Printf(" SCENE | %s\n", note) +} + +func hdr(s string) { + fmt.Printf("\n== %s ==\n", s) +} + +func methodURL(method string) string { + base := strings.TrimRight(cfg.BaseURL, "/") + return fmt.Sprintf("%s/capsets/%s/connect/%s/zhizhangyi.mbs.UserManagement/%s", base, cfg.Capset, cfg.Instance, method) +} + +func call(method string, body map[string]any) (map[string]any, int, error) { + buf, err := json.Marshal(body) + if err != nil { + return nil, 0, err + } + req, err := http.NewRequest(http.MethodPost, methodURL(method), bytes.NewReader(buf)) + if err != nil { + return nil, 0, err + } + req.Header.Set("Content-Type", "application/json") + if cfg.Token != "" { + req.Header.Set("Authorization", "Bearer "+cfg.Token) + } + cli := &http.Client{Timeout: 30 * time.Second} + resp, err := cli.Do(req) + if err != nil { + return nil, 0, err + } + defer resp.Body.Close() + raw, err := io.ReadAll(resp.Body) + if err != nil { + return nil, resp.StatusCode, err + } + var out map[string]any + if len(raw) == 0 { + out = map[string]any{} + } else if err := json.Unmarshal(raw, &out); err != nil { + return map[string]any{"raw": string(raw)}, resp.StatusCode, fmt.Errorf("decode response: %w", err) + } + if resp.StatusCode >= 300 { + return out, resp.StatusCode, fmt.Errorf("http=%d", resp.StatusCode) + } + if code, ok := out["code"].(string); ok && code != "" { + return out, resp.StatusCode, fmt.Errorf("connect_code=%s message=%s", code, gs(out, "message")) + } + return out, resp.StatusCode, nil +} + +func detail(uid string) (map[string]any, error) { + r, _, err := call("DetailUser", map[string]any{"userId": uid}) + return r, err +} + +func userDetail(uid string) (map[string]any, error) { + r, err := detail(uid) + if err != nil { + return nil, err + } + return gm(r, "data"), nil +} + +func scenario1() { + hdr("场景1: 停用 test2,不自动恢复") + scene("场景备注:模拟管理员手动停用 test2,执行后保留停用状态,方便去 MBS 前端观察启停变化") + _, _, err := call("StateUsers", map[string]any{"userIds": []string{uid2}, "type": 0, "state": "0"}) + if err != nil { + fl("StateUsers failed: %v", err) + return + } + ok("StateUsers returned success") + d, err := userDetail(uid2) + if err != nil { + fl("DetailUser verify failed: %v", err) + return + } + if gf(d, "state") == 0 { + ok("test2.state=0") + } else { + fl("test2.state=%.0f", gf(d, "state")) + } + inf("已停用 test2,当前脚本不会自动恢复,便于前端观察状态变化") +} + +func scenario2() { + hdr("场景2: 停用 test1,不自动恢复") + scene("场景备注:模拟管理员手动停用 test1,执行后保留停用状态,方便去 MBS 前端观察启停变化") + _, _, err := call("StateUsers", map[string]any{"userIds": []string{uid1}, "type": 0, "state": "0"}) + if err != nil { + fl("StateUsers failed: %v", err) + return + } + ok("StateUsers returned success") + d, err := userDetail(uid1) + if err != nil { + fl("DetailUser verify failed: %v", err) + return + } + if gf(d, "state") == 0 { + ok("test1.state=0") + } else { + fl("test1.state=%.0f", gf(d, "state")) + } + inf("已停用 test1,当前脚本不会自动恢复,便于前端观察状态变化") +} + +func scenario3() { + hdr("场景3: 开启 test1 设备管控") + scene("场景备注:模拟管理员给 test1 开启设备管控,用于前端观察 isMdm 或设备管理状态变化") + _, _, err := call("UpdUser", map[string]any{ + "userId": uid1, + "userName": "测试1", + "loginName": "test1", + "deptId": "1", + "isMdm": 1, + }) + if err != nil { + fl("UpdUser failed: %v", err) + return + } + ok("UpdUser returned success") + d, err := userDetail(uid1) + if err != nil { + fl("DetailUser verify failed: %v", err) + return + } + if gf(d, "isMdm") == 1 { + ok("test1.isMdm=1") + } else { + fl("test1.isMdm=%.0f", gf(d, "isMdm")) + } + inf("test1.state=%.0f", gf(d, "state")) +} + +func tGetUsers() { + hdr("6.2.1 GetUsers") + scene("场景备注:查询用户列表,适合先确认 OctoBus 到 MBS 的读取链路已经打通") + r, _, err := call("GetUsers", map[string]any{ + "index": 0, + "size": 10, + "condition": map[string]any{ + "deptId": "1", + "keyWord": "", + }, + }) + if err != nil { + fl("GetUsers failed: %v", err) + return + } + d := gm(r, "data") + inf("total=%.0f", gf(d, "total")) + if gf(d, "total") >= 2 { + ok("total>=2") + } else { + fl("total=%.0f", gf(d, "total")) + } +} + +func tAddUser() { + hdr("6.2.2 AddUser") + scene("场景备注:新增一个测试用户,适合验证创建类接口;执行前建议先确认账号命名规则和回收策略") + payload := map[string]any{ + "userName": "OctoBus新增用户", + "loginName": fmt.Sprintf("octobus_add_%d", time.Now().Unix()), + "deptId": "1", + "password": "123456", + "phoneNumber": "13800138111", + "userSource": 0, + "isMdm": 0, + "state": 1, + "organization": "OctoBus", + "employeeNumber": fmt.Sprintf("E%d", time.Now().Unix()%100000), + } + r, _, err := call("AddUser", payload) + if err != nil { + fl("AddUser failed: %v", err) + inf("payload=%s", mustJSON(payload)) + return + } + ok("AddUser returned success") + inf("response=%s", mustJSON(r)) +} + +func tUpdUser() { + hdr("6.2.3 UpdUser") + scene("场景备注:修改 test1 的基础字段,适合验证用户编辑能力是否可通过 OctoBus 正常下发") + _, _, err := call("UpdUser", map[string]any{ + "userId": uid1, + "userName": "测试1", + "loginName": "test1", + "deptId": "1", + "weight": 1, + }) + if err != nil { + fl("UpdUser failed: %v", err) + return + } + ok("UpdUser returned success") +} + +func tDetailUser() { + hdr("6.2.4 DetailUser") + scene("场景备注:查看 test1 当前详情,适合在做写操作前先确认用户当前状态") + r, _, err := call("DetailUser", map[string]any{"userId": uid1}) + if err != nil { + fl("DetailUser failed: %v", err) + return + } + d := gm(r, "data") + inf("loginName=%s state=%.0f isMdm=%.0f", gs(d, "loginName"), gf(d, "state"), gf(d, "isMdm")) + if gs(d, "loginName") == "test1" { + ok("loginName=test1") + } else { + fl("loginName=%s", gs(d, "loginName")) + } +} + +func tDelUsers() { + hdr("6.2.5 DelUsers") + scene("场景备注:删除指定测试用户,适合验证删除链路;执行前必须先把目标 userId 换成你自己的测试账号") + payload := map[string]any{"userIds": []string{"replace-with-user-id"}, "type": 0} + r, _, err := call("DelUsers", payload) + if err != nil { + fl("DelUsers failed: %v", err) + inf("payload=%s", mustJSON(payload)) + return + } + ok("DelUsers returned success") + inf("response=%s", mustJSON(r)) +} + +func tStateUsers() { + hdr("6.2.6 StateUsers") + scene("场景备注:演示用户启停接口,这里默认把 test2 设为启用,可作为停用后的恢复动作") + _, _, err := call("StateUsers", map[string]any{"userIds": []string{uid2}, "type": 0, "state": "1"}) + if err != nil { + fl("StateUsers failed: %v", err) + return + } + ok("StateUsers returned success") +} + +func tCheckLoginName() { + hdr("6.2.7 CheckLoginName") + scene("场景备注:同时验证一个已存在账号和一个新账号,适合检查业务错误映射和可用性判断") + r1, _, err1 := call("CheckLoginName", map[string]any{"loginName": "test1"}) + if err1 != nil { + inf("test1 occupied, resp=%s err=%v", mustJSON(r1), err1) + ok("existing login name returned business error as expected") + } else { + fl("test1 unexpectedly passed, resp=%s", mustJSON(r1)) + } + r2, _, err2 := call("CheckLoginName", map[string]any{"loginName": fmt.Sprintf("octobus_free_%d", time.Now().Unix())}) + if err2 != nil { + fl("new login name failed: %v", err2) + return + } + ok("new login name returned success") + inf("response=%s", mustJSON(r2)) +} + +func tGetUserByPhone() { + hdr("6.2.8 GetUserByPhone") + scene("场景备注:按手机号查询用户,适合验证手机号索引查询能力是否正常") + r, _, err := call("GetUserByPhone", map[string]any{"phone": "13800138000"}) + if err != nil { + fl("GetUserByPhone failed: %v", err) + return + } + ok("GetUserByPhone returned success") + inf("response=%s", mustJSON(r)) +} + +func tUpdUserPwd() { + hdr("6.2.9/6.2.10 UpdUserPwd") + scene("场景备注:修改用户密码,适合验证敏感写操作;执行前要先准备好 3DES 加密后的密码串") + payload := map[string]any{ + "userId": uid1, + "password": "replace-with-3des-password", + "version": "v1", + } + r, _, err := call("UpdUserPwd", payload) + if err != nil { + fl("UpdUserPwd failed: %v", err) + inf("payload=%s", mustJSON(payload)) + return + } + ok("UpdUserPwd returned success") + inf("response=%s", mustJSON(r)) +} + +func tForceOffline() { + hdr("6.2.11 ForceOffline") + scene("场景备注:强制指定用户下线,适合验证在线会话踢出能力;前提是目标用户当前在线") + r, _, err := call("ForceOffline", map[string]any{"userId": uid1}) + if err != nil { + fl("ForceOffline failed: %v", err) + return + } + ok("ForceOffline returned success") + inf("response=%s", mustJSON(r)) +} + +func tImportUser() { + hdr("6.2.12 ImportUser") + scene("场景备注:通过 fileId 导入用户,适合验证批量导入能力;前提是你已经先完成文件上传") + payload := map[string]any{"lang": 2, "fileId": "replace-with-uploaded-file-id"} + r, _, err := call("ImportUser", payload) + if err != nil { + fl("ImportUser failed: %v", err) + inf("payload=%s", mustJSON(payload)) + return + } + ok("ImportUser returned success") + inf("response=%s", mustJSON(r)) +} + +func mustJSON(v any) string { + b, err := json.Marshal(v) + if err != nil { + return fmt.Sprintf("marshal-error:%v", err) + } + return string(b) +} + +func main() { + baseURL := flag.String("base-url", cfg.BaseURL, "OctoBus base URL") + capset := flag.String("capset", cfg.Capset, "OctoBus capset ID") + instance := flag.String("instance", cfg.Instance, "OctoBus instance ID") + token := flag.String("token", cfg.Token, "OctoBus bearer token") + tn := flag.String("test", "all", "test name: all|scenarios|apis|scenario1|getUsers|...") + flag.Parse() + + cfg.BaseURL = *baseURL + cfg.Capset = *capset + cfg.Instance = *instance + cfg.Token = *token + + all := map[string]func(){ + "scenario1": scenario1, + "scenario2": scenario2, + "scenario3": scenario3, + "getUsers": tGetUsers, + "addUser": tAddUser, + "updUser": tUpdUser, + "detailUser": tDetailUser, + "delUsers": tDelUsers, + "stateUsers": tStateUsers, + "checkLoginName": tCheckLoginName, + "getUserByPhone": tGetUserByPhone, + "updUserPwd": tUpdUserPwd, + "forceOffline": tForceOffline, + "importUser": tImportUser, + } + + run := func(keys []string) { + for _, k := range keys { + all[k]() + } + } + + switch *tn { + case "scenarios": + run([]string{"scenario1", "scenario2", "scenario3"}) + case "apis": + run([]string{"getUsers", "addUser", "updUser", "detailUser", "delUsers", "stateUsers", "checkLoginName", "getUserByPhone", "updUserPwd", "forceOffline", "importUser"}) + case "all": + run([]string{"scenario1", "scenario2", "scenario3", "getUsers", "addUser", "updUser", "detailUser", "delUsers", "stateUsers", "checkLoginName", "getUserByPhone", "updUserPwd", "forceOffline", "importUser"}) + default: + fn, ok := all[*tn] + if !ok { + fmt.Fprintf(os.Stderr, "unknown test: %s\n", *tn) + os.Exit(1) + } + fn() + } + + fmt.Printf("\n===================================\n") + fmt.Printf("Results: %d passed, %d failed\n", pass, failn) + if failn > 0 { + os.Exit(1) + } +} diff --git a/services/zhizhangyi__mbs/testdata/test_4apis.mjs b/services/zhizhangyi__mbs/testdata/test_4apis.mjs new file mode 100644 index 00000000..4b970bb9 --- /dev/null +++ b/services/zhizhangyi__mbs/testdata/test_4apis.mjs @@ -0,0 +1,123 @@ +// MBS API 测试: getUsers / detailUser / checkLoginName / stateUsers +import crypto from 'node:crypto'; +import https from 'node:https'; + +const C = { + baseUrl: 'https://101.37.25.245:9074', + orgCode: 'test', + appkey: 'jiekoudiaoyongappkey', + secretkey: 'jiekoudiaoyongsecretkey', +}; +const BASE = `${C.baseUrl}/uusafe/mos/thirdaccess/rest/opt`; +const agent = new https.Agent({ rejectUnauthorized: false }); +const md5 = (s) => crypto.createHash('md5').update(s, 'utf8').digest('hex'); +const sign = (...ps) => md5(ps.map((p) => (p ?? '')).join('') + C.secretkey); + +const post = async (path, body) => { + const res = await fetch(`${BASE}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(body), agent }); + const text = await res.text(); + return { status: res.status, json: JSON.parse(text) }; +}; + +(async () => { + const { appkey, orgCode } = C; + + // ── Test 1: getUsers ── + console.log('══════════════════════════════════════════'); + console.log('Test 1: getUsers (用户列表)'); + console.log('══════════════════════════════════════════'); + try { + const r1 = await post('/v1/getUsers', { + index: 0, size: 10, orderCode: 0, orderType: 1, + condition: { deptId: '1', keyWord: '' }, + orgCode, appkey, + sign: sign(appkey, orgCode, 0, 10, 0, 1, '', '', '', '1'), + }); + console.log(`HTTP ${r1.status} code=${r1.json.code}`); + if (r1.json.code === 0) { + console.log(` 共 ${r1.json.data.total} 个用户`); + (r1.json.data.userInfos || []).forEach((u, i) => + console.log(` [${i + 1}] ${u.userName} (${u.loginName}) userId=${u.userId} state=${u.state}`)); + } else { console.log(` ❌ ${r1.json.msg}`); } + } catch (e) { console.log(` ❌ ${e.message}`); } + + // ── Test 2: detailUser (test1) ── + console.log('\n══════════════════════════════════════════'); + console.log('Test 2: detailUser (用户详情 - test1)'); + console.log('══════════════════════════════════════════'); + const uid = '1691979294102310912'; // test1 + try { + const r2 = await post('/v1/detailUser', { + userId: uid, orgCode, appkey, + sign: sign(appkey, orgCode, uid), + }); + console.log(`HTTP ${r2.status} code=${r2.json.code}`); + if (r2.json.code === 0) { + const d = r2.json.data; + console.log(` ID: ${d.userId}`); + console.log(` 姓名: ${d.userName}`); + console.log(` 登录名: ${d.loginName}`); + console.log(` 部门: ${d.deptName} (${d.deptId})`); + console.log(` 手机: ${d.phoneNumber || '(空)'}`); + console.log(` 邮箱: ${d.email || '(空)'}`); + console.log(` 状态: ${d.state === 1 ? '启用' : d.state === 0 ? '停用' : '未激活'}`); + console.log(` 设备管理: ${d.isMdm ? '开启' : '关闭'}`); + } else { console.log(` ❌ ${r2.json.msg}`); } + } catch (e) { console.log(` ❌ ${e.message}`); } + + // ── Test 3: checkLoginName ── + console.log('\n══════════════════════════════════════════'); + console.log('Test 3: checkLoginName (账号校验)'); + console.log('══════════════════════════════════════════'); + for (const name of ['test1', 'nonexistent_user']) { + try { + const r3 = await post('/v1/checkLoginName', { + loginName: name, orgCode, appkey, + sign: sign(appkey, orgCode, name), + }); + const ok = r3.json.code === 0; + console.log(` "${name}" -> HTTP ${r3.status} code=${r3.json.code} ${ok ? '✅ 可用' : '❌ ' + r3.json.msg}`); + } catch (e) { console.log(` "${name}" -> ❌ ${e.message}`); } + } + + // ── Test 4: stateUsers (启停 - 先停用 test2 再启用) ── + console.log('\n══════════════════════════════════════════'); + console.log('Test 4: stateUsers (用户启停 - test2)'); + console.log('══════════════════════════════════════════'); + const uid2 = '1691979421122613248'; // test2 + + // 4a: 停用 + console.log(' 4a: 停用 test2...'); + try { + const r4a = await post('/v1/stateUsers', { + userIds: [uid2], type: 0, state: '0', + orgCode, appkey, + sign: sign(appkey, orgCode, uid2, 0, '0', ''), + }); + console.log(` HTTP ${r4a.status} code=${r4a.json.code} ${r4a.json.code === 0 ? '✅ 停用成功' : '❌ ' + r4a.json.msg}`); + } catch (e) { console.log(` ❌ ${e.message}`); } + + // 4b: 验证 - 查详情看 state + try { + const r4b = await post('/v1/detailUser', { + userId: uid2, orgCode, appkey, + sign: sign(appkey, orgCode, uid2), + }); + console.log(` 4b: 验证停用后详情 state=${r4b.json.data?.state} ${r4b.json.data?.state === 0 ? '✅ 已停用' : '⚠️'}`); + } catch (e) { console.log(` 4b: ❌ ${e.message}`); } + + // 4c: 重新启用 + console.log(' 4c: 重新启用 test2...'); + try { + const r4c = await post('/v1/stateUsers', { + userIds: [uid2], type: 0, state: '1', + orgCode, appkey, + sign: sign(appkey, orgCode, uid2, 0, '1', ''), + }); + console.log(` HTTP ${r4c.status} code=${r4c.json.code} ${r4c.json.code === 0 ? '✅ 重新启用成功' : '❌ ' + r4c.json.msg}`); + } catch (e) { console.log(` ❌ ${e.message}`); } + + console.log('\n══════════════════════════════════════════'); + console.log('全部测试完成'); + console.log('══════════════════════════════════════════'); +})(); diff --git a/services/zhizhangyi__mbs/testdata/test_getusers.mjs b/services/zhizhangyi__mbs/testdata/test_getusers.mjs new file mode 100644 index 00000000..642ff436 --- /dev/null +++ b/services/zhizhangyi__mbs/testdata/test_getusers.mjs @@ -0,0 +1,96 @@ +// MBS getUsers API 直连测试 +// 不依赖 OctoBus SDK,直接用 Node.js 原生 fetch 调用 + +import crypto from 'node:crypto'; +import https from 'node:https'; + +const CONFIG = { + baseUrl: 'https://101.37.25.245:9074', + orgCode: 'test', + appkey: 'jiekoudiaoyongappkey', + secretkey: 'jiekoudiaoyongsecretkey', +}; + +const md5 = (s) => crypto.createHash('md5').update(s, 'utf8').digest('hex'); + +// sign = MD5(appkey + orgCode + index + size + orderCode + orderType + keyword + state + isMdm + deptId + secretkey) +// 拼接时不包含 "+" +const computeSign = (params) => { + const joined = params.map((p) => (p === undefined || p === null ? '' : String(p))).join(''); + return md5(joined + CONFIG.secretkey); +}; + +const testGetUsers = async () => { + const { baseUrl, orgCode, appkey } = CONFIG; + + const index = 0; + const size = 10; + const orderCode = 0; + const orderType = 1; + const keyword = ''; + const state = ''; + const isMdm = ''; + const deptId = '1'; + + const sign = computeSign([appkey, orgCode, index, size, orderCode, orderType, keyword, state, isMdm, deptId]); + + const body = { + index, + size, + orderCode, + orderType, + condition: { deptId, keyWord: keyword }, + orgCode, + appkey, + sign, + }; + + console.log('=== MBS getUsers 测试 ==='); + console.log('URL:', `${baseUrl}/uusafe/mos/thirdaccess/rest/opt/v1/getUsers`); + console.log('Body:', JSON.stringify(body, null, 2)); + console.log('Sign:', sign); + console.log(''); + + // 忽略自签名证书 + const agent = new https.Agent({ rejectUnauthorized: false }); + + try { + const res = await fetch(`${baseUrl}/uusafe/mos/thirdaccess/rest/opt/v1/getUsers`, { + method: 'POST', + headers: { 'Content-Type': 'application/json; charset=utf-8' }, + body: JSON.stringify(body), + agent, + }); + + const text = await res.text(); + console.log('HTTP Status:', res.status); + console.log('Response headers:', Object.fromEntries(res.headers.entries())); + + let json; + try { + json = JSON.parse(text); + console.log('\n✅ 响应 JSON:'); + console.log(JSON.stringify(json, null, 2)); + + if (json.code === 0) { + console.log(`\n✅ 成功!共 ${json.data?.total ?? 0} 个用户`); + const users = json.data?.userInfos; + if (Array.isArray(users)) { + users.forEach((u, i) => { + console.log(` [${i + 1}] ${u.userName} (${u.loginName}) - ${u.deptName} - ${u.state === 1 ? '启用' : u.state === 0 ? '停用' : '未激活'}`); + }); + } + } else { + console.log(`\n❌ MBS 返回错误: code=${json.code}, msg=${json.msg}`); + } + } catch { + console.log('\n⚠️ 非 JSON 响应:'); + console.log(text.substring(0, 500)); + } + } catch (e) { + console.log('\n❌ 网络错误:', e.message); + if (e.cause) console.log(' 原因:', e.cause.message); + } +}; + +testGetUsers(); diff --git a/services/zhizhangyi__mbs/testdata/test_mbs_client.go b/services/zhizhangyi__mbs/testdata/test_mbs_client.go new file mode 100644 index 00000000..2bdb60b0 --- /dev/null +++ b/services/zhizhangyi__mbs/testdata/test_mbs_client.go @@ -0,0 +1,267 @@ +// MBS API Test Client (Go) - 直接调用 MBS REST API +// 用法见 HELP.md +package main + +import ( + "bytes" + "crypto/md5" + "crypto/tls" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" +) + +var cfg = struct{ B, O, A, S string }{"https://101.37.25.245:9074", "test", "jiekoudiaoyongappkey", "jiekoudiaoyongsecretkey"} + +const apiBase = "/uusafe/mos/thirdaccess/rest/opt" +const uid1 = "1691979294102310912" // test1 +const uid2 = "1691979421122613248" // test2 + +var pass, failn, skipn int + +func md5s(s string) string { h := md5.Sum([]byte(s)); return fmt.Sprintf("%x", h) } +func sign(p ...string) string { return md5s(strings.Join(p, "") + cfg.S) } +func gf(m map[string]interface{}, k string) float64 { f, _ := m[k].(float64); return f } +func gs(m map[string]interface{}, k string) string { s, _ := m[k].(string); return s } +func ok(s string, a ...interface{}) { pass++; fmt.Printf(" ✅ PASS | "+s+"\n", a...) } +func fl(s string, a ...interface{}) { failn++; fmt.Printf(" ❌ FAIL | "+s+"\n", a...) } +func sk(s string, a ...interface{}) { skipn++; fmt.Printf(" ⏭️ SKIP | "+s+"\n", a...) } +func inf(s string, a ...interface{}) { fmt.Printf(" ℹ️ "+s+"\n", a...) } +func hdr(s string) { fmt.Printf("\n── %s ──\n", s) } + +func call(path string, body map[string]interface{}) (map[string]interface{}, error) { + b, _ := json.Marshal(body) + req, _ := http.NewRequest("POST", cfg.B+apiBase+path, bytes.NewReader(b)) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + cl := &http.Client{Timeout: 30 * time.Second, + Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} + resp, err := cl.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + rb, _ := io.ReadAll(resp.Body) + var r map[string]interface{} + json.Unmarshal(rb, &r) + if c, _ := r["code"].(float64); c != 0 { + return r, fmt.Errorf("code=%.0f", c) + } + return r, nil +} + +func detail(uid string) (map[string]interface{}, error) { + return call("/v1/detailUser", map[string]interface{}{ + "userId": uid, "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, uid)}) +} + +// ═══ 场景1: test2 停用 (state=0) ═══ +func s1() { + hdr("场景1: test2 用户停用 (state=0)") + inf("接口:stateUsers | 入参:userIds=[uid2],type=0,state=0 | 目标:code=0, state=0") + _, e := call("/v1/stateUsers", map[string]interface{}{ + "userIds": []string{uid2}, "type": 0, "state": "0", + "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, uid2, "0", "0", "")}) + if e != nil { + fl("失败:%v", e) + return + } + ok("code=0") + da, _ := detail(uid2) + d := da["data"].(map[string]interface{}) + if gf(d, "state") == 0 { + ok("test2.state=0 已停用") + } else { + fl("state=%.0f", gf(d, "state")) + } + call("/v1/stateUsers", map[string]interface{}{ + "userIds": []string{uid2}, "type": 0, "state": "1", + "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, uid2, "0", "1", "")}) + inf("已恢复 test2") +} + +// ═══ 场景2: test1 停用 (state=0) ═══ +func s2() { + hdr("场景2: test1 停用 (state=0)") + inf("接口:stateUsers | 入参:userIds=[uid1],type=0,state=0 | 目标:code=0, state=0") + _, e := call("/v1/stateUsers", map[string]interface{}{ + "userIds": []string{uid1}, "type": 0, "state": "0", + "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, uid1, "0", "0", "")}) + if e != nil { + fl("失败:%v", e) + return + } + ok("code=0") + da, _ := detail(uid1) + d := da["data"].(map[string]interface{}) + if gf(d, "state") == 0 { + ok("test1.state=0 已停用") + } else { + fl("state=%.0f", gf(d, "state")) + } + call("/v1/stateUsers", map[string]interface{}{ + "userIds": []string{uid1}, "type": 0, "state": "1", + "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, uid1, "0", "1", "")}) + inf("已恢复 test1") +} + +// ═══ 场景3: test1 开启设备管控 (isMdm=1) ═══ +func s3() { + hdr("场景3: test1 开启设备管控 (isMdm=1)") + inf("接口:updUser | 入参:userId=uid1,isMdm=1 | 目标:code=0,isMdm=1,state保持1") + _, e := call("/v1/updUser", map[string]interface{}{ + "userId": uid1, "userName": "测试1", "loginName": "test1", "deptId": "1", + "isMdm": 1, "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, uid1, "测试1", "test1", "1")}) + if e != nil { + fl("失败:%v", e) + return + } + ok("code=0") + da, _ := detail(uid1) + d := da["data"].(map[string]interface{}) + if gf(d, "isMdm") == 1 { + ok("isMdm=1 设备管控已开启") + } else { + fl("isMdm=%.0f", gf(d, "isMdm")) + } + inf("state=%.0f (应保持1)", gf(d, "state")) +} + +// 6.2.1 GetUsers +func tGetUsers() { + hdr("6.2.1 GetUsers - 用户列表") + inf("入参:index=0,size=10 | 目标:total>=2") + r, e := call("/v1/getUsers", map[string]interface{}{ + "index": 0, "size": 10, "orderCode": 0, "orderType": 1, + "condition": map[string]interface{}{"deptId": "1", "keyWord": ""}, + "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, "0", "10", "0", "1", "", "", "", "1")}) + if e != nil { + fl("失败:%v", e) + return + } + d := r["data"].(map[string]interface{}) + inf("total=%.0f", gf(d, "total")) + if gf(d, "total") >= 2 { + ok("total>=2") + } else { + fl("total=%.0f", gf(d, "total")) + } +} + +// 6.2.4 DetailUser +func tDetailUser() { + hdr("6.2.4 DetailUser - 用户详情") + r, e := detail(uid1) + if e != nil { + fl("失败:%v", e) + return + } + d := r["data"].(map[string]interface{}) + inf("userName=%s state=%.0f isMdm=%.0f", gs(d, "userName"), gf(d, "state"), gf(d, "isMdm")) + if gs(d, "loginName") == "test1" { + ok("loginName=test1") + } else { + fl("loginName=%s", gs(d, "loginName")) + } +} + +// 6.2.6 StateUsers +func tStateUsers() { + hdr("6.2.6 StateUsers - 启停用户") + _, e := call("/v1/stateUsers", map[string]interface{}{ + "userIds": []string{uid2}, "type": 0, "state": "1", + "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, uid2, "0", "1", "")}) + if e != nil { + fl("失败:%v", e) + } else { + ok("code=0") + } +} + +// 6.2.7 CheckLoginName +func tCheckLoginName() { + hdr("6.2.7 CheckLoginName - 账号校验") + inf("test1(已存在)→存在 | nonexistent_user→可用") + r2, _ := call("/v1/checkLoginName", map[string]interface{}{ + "loginName": "test1", "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, "test1")}) + inf("test1: code=%.0f msg=%s", gf(r2, "code"), gs(r2, "msg")) + _, e := call("/v1/checkLoginName", map[string]interface{}{ + "loginName": "nonexistent_user", "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, "nonexistent_user")}) + if e != nil { + fl("失败:%v", e) + } else { + ok("nonexistent_user 可用") + } +} + +// 6.2.8 GetUserByPhone +func tGetUserByPhone() { + hdr("6.2.8 GetUserByPhone - 手机号查询") + _, e := call("/v1/getUserByPhone", map[string]interface{}{ + "phone": "13800138000", "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, "13800138000")}) + if e != nil { + fl("失败:%v", e) + } else { + ok("code=0") + } +} + +// 6.2.3 UpdUser +func tUpdUser() { + hdr("6.2.3 UpdUser - 编辑用户") + _, e := call("/v1/updUser", map[string]interface{}{ + "userId": uid1, "userName": "测试1", "loginName": "test1", "deptId": "1", + "weight": 1, "orgCode": cfg.O, "appkey": cfg.A, "sign": sign(cfg.A, cfg.O, uid1, "测试1", "test1", "1")}) + if e != nil { + fl("失败:%v", e) + } else { + ok("code=0") + } +} + +// 其余只写/危险操作跳过 +func tAddUser() { hdr("6.2.2 AddUser"); sk("生产环境跳过写操作") } +func tDelUsers() { hdr("6.2.5 DelUsers"); sk("生产环境跳过写操作") } +func tUpdUserPwd() { hdr("6.2.9/6.2.10 UpdUserPwd"); sk("生产环境跳过写操作") } +func tForceOffline() { hdr("6.2.11 ForceOffline"); sk("test1无在线会话") } +func tImportUser() { hdr("6.2.12 ImportUser"); sk("需上传文件") } + +func main() { + tn := flag.String("test", "all", "test name: all|scenarios|apis|scenario1|getUsers|...") + flag.Parse() + all := map[string]func(){ + "scenario1": s1, "scenario2": s2, "scenario3": s3, + "getUsers": tGetUsers, "addUser": tAddUser, "updUser": tUpdUser, + "detailUser": tDetailUser, "delUsers": tDelUsers, "stateUsers": tStateUsers, + "checkLoginName": tCheckLoginName, "getUserByPhone": tGetUserByPhone, + "updUserPwd": tUpdUserPwd, "forceOffline": tForceOffline, "importUser": tImportUser} + run := func(keys []string) { + for _, k := range keys { + all[k]() + } + } + switch *tn { + case "scenarios": + run([]string{"scenario1", "scenario2", "scenario3"}) + case "apis": + run([]string{"getUsers", "addUser", "updUser", "detailUser", "delUsers", "stateUsers", "checkLoginName", "getUserByPhone", "updUserPwd", "forceOffline", "importUser"}) + case "all": + run([]string{"scenario1", "scenario2", "scenario3", "getUsers", "addUser", "updUser", "detailUser", "delUsers", "stateUsers", "checkLoginName", "getUserByPhone", "updUserPwd", "forceOffline", "importUser"}) + default: + if fn, ok := all[*tn]; ok { + fn() + } else { + fmt.Fprintf(os.Stderr, "unknown test: %s\n", *tn) + os.Exit(1) + } + } + fmt.Printf("\n═══════════════════════════════════\n") + fmt.Printf("Results: %d passed, %d failed, %d skipped\n", pass, failn, skipn) + if failn > 0 { + os.Exit(1) + } +} diff --git a/services/zhizhangyi__mbs/testdata/test_scenarios.mjs b/services/zhizhangyi__mbs/testdata/test_scenarios.mjs new file mode 100644 index 00000000..42b2a52c --- /dev/null +++ b/services/zhizhangyi__mbs/testdata/test_scenarios.mjs @@ -0,0 +1,94 @@ +// MBS 场景测试: test2停用 / test1设备激活停用 / test1开启设备管控 +import crypto from 'node:crypto'; +import https from 'node:https'; + +const C = { + baseUrl: 'https://101.37.25.245:9074', + orgCode: 'test', + appkey: 'jiekoudiaoyongappkey', + secretkey: 'jiekoudiaoyongsecretkey', +}; +const BASE = `${C.baseUrl}/uusafe/mos/thirdaccess/rest/opt`; +const agent = new https.Agent({ rejectUnauthorized: false }); +const md5 = (s) => crypto.createHash('md5').update(s, 'utf8').digest('hex'); +const sign = (...ps) => md5(ps.map((p) => (p ?? '')).join('') + C.secretkey); + +const post = async (path, body) => { + const res = await fetch(`${BASE}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(body), agent }); + const text = await res.text(); + return { status: res.status, json: JSON.parse(text) }; +}; + +const { appkey, orgCode } = C; +const uid1 = '1691979294102310912'; // test1 +const uid2 = '1691979421122613248'; // test2 + +// 辅助: 查详情 +const detail = async (uid) => { + const r = await post('/v1/detailUser', { userId: uid, orgCode, appkey, sign: sign(appkey, orgCode, uid) }); + return r.json?.data; +}; + +(async () => { + // 先查初始状态 + console.log('=== 初始状态 ==='); + let d1 = await detail(uid1); + let d2 = await detail(uid2); + console.log(`test1: state=${d1.state} isMdm=${d1.isMdm}`); + console.log(`test2: state=${d2.state} isMdm=${d2.isMdm}`); + + // ═══ 场景1: test2 用户状态 → 停用 (state=0) ═══ + console.log('\n═══ 场景1: test2 用户状态 → 停用 (state=0) ═══'); + const r1 = await post('/v1/stateUsers', { + userIds: [uid2], type: 0, state: '0', + orgCode, appkey, + sign: sign(appkey, orgCode, uid2, 0, '0', ''), + }); + console.log(` stateUsers -> HTTP ${r1.status} code=${r1.json.code} ${r1.json.code === 0 ? '✅' : '❌'}`); + d2 = await detail(uid2); + console.log(` 验证: test2 state=${d2.state} ${d2.state === 0 ? '✅ 已停用' : '⚠️'}`); + + // 恢复 test2 + await post('/v1/stateUsers', { + userIds: [uid2], type: 0, state: '1', + orgCode, appkey, + sign: sign(appkey, orgCode, uid2, 0, '1', ''), + }); + + // ═══ 场景2: test1 设备激活状态 → 停用 (state=0) ═══ + console.log('\n═══ 场景2: test1 设备激活状态 → 停用 (state=0) ═══'); + const r2 = await post('/v1/stateUsers', { + userIds: [uid1], type: 0, state: '0', + orgCode, appkey, + sign: sign(appkey, orgCode, uid1, 0, '0', ''), + }); + console.log(` stateUsers -> HTTP ${r2.status} code=${r2.json.code} ${r2.json.code === 0 ? '✅' : '❌'}`); + d1 = await detail(uid1); + console.log(` 验证: test1 state=${d1.state} ${d1.state === 0 ? '✅ 已停用' : '⚠️'}`); + + // 恢复 test1 + await post('/v1/stateUsers', { + userIds: [uid1], type: 0, state: '1', + orgCode, appkey, + sign: sign(appkey, orgCode, uid1, 0, '1', ''), + }); + + // ═══ 场景3: test1 开启设备管控 (isMdm=1) ═══ + console.log('\n═══ 场景3: test1 开启设备管控 (isMdm=1) ═══'); + console.log(' 调用 updUser 设置 isMdm=1 ...'); + const r3 = await post('/v1/updUser', { + userId: uid1, + userName: '测试1', + loginName: 'test1', + deptId: '1', + isMdm: 1, + orgCode, appkey, + sign: sign(appkey, orgCode, uid1, '测试1', 'test1', '1'), + }); + console.log(` updUser -> HTTP ${r3.status} code=${r3.json.code} ${r3.json.code === 0 ? '✅' : '❌ ' + r3.json.msg}`); + d1 = await detail(uid1); + console.log(` 验证: test1 isMdm=${d1.isMdm} ${d1.isMdm === 1 ? '✅ 设备管控已开启' : '⚠️'}`); + console.log(` test1 state=${d1.state} (应仍为1-启用)`); + + console.log('\n=== 全部场景测试完成 ==='); +})(); From 2fd2224231a2c1df9714c3b9b2f551c49f9b0f91 Mon Sep 17 00:00:00 2001 From: "tongzhou.li" Date: Tue, 30 Jun 2026 22:31:42 +0800 Subject: [PATCH 3/3] Address Zhizhangyi MBS review findings Signed-off-by: tongzhou.li --- internal/supervisor/supervisor.go | 16 +++++++++-- services/zhizhangyi__mbs/config.schema.json | 7 ++--- services/zhizhangyi__mbs/secret.schema.json | 7 ++--- .../zhizhangyi__mbs/testdata/test_4apis.mjs | 15 +++++++--- .../testdata/test_getusers.mjs | 16 ++++++++--- .../testdata/test_mbs_client.go | 28 ++++++++++++++++++- .../testdata/test_scenarios.mjs | 15 +++++++--- 7 files changed, 80 insertions(+), 24 deletions(-) diff --git a/internal/supervisor/supervisor.go b/internal/supervisor/supervisor.go index b8e24786..db7c8ff0 100644 --- a/internal/supervisor/supervisor.go +++ b/internal/supervisor/supervisor.go @@ -41,6 +41,7 @@ type processState struct { done chan struct{} attempt int generation int64 + cleanup func() } type CreateInstanceRequest struct { @@ -236,7 +237,7 @@ func (s *Supervisor) startWithAttempt(ctx context.Context, instanceID string, re startErr = err return err } - defer closeSecret() + cleanupSecret := closeSecret args := []string{"--runtime", "serve", "--host", "127.0.0.1", "--port", fmt.Sprintf("%d", port), "--config", filepath.Join(workdir, "config.json")} args = append(args, secretArg...) args = append(args, "--workdir", workdir, "--service", svc.ID, "--instance", instanceID) @@ -254,12 +255,14 @@ func (s *Supervisor) startWithAttempt(ctx context.Context, instanceID string, re ) stdout, err := os.OpenFile(filepath.Join(workdir, "stdout.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) if err != nil { + cleanupSecret() startErr = err return err } stderr, err := os.OpenFile(filepath.Join(workdir, "stderr.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) if err != nil { _ = stdout.Close() + cleanupSecret() startErr = err return err } @@ -271,12 +274,14 @@ func (s *Supervisor) startWithAttempt(ctx context.Context, instanceID string, re if err := s.Store.UpsertInstance(ctx, inst); err != nil { _ = stdout.Close() _ = stderr.Close() + cleanupSecret() startErr = err return err } if err := cmd.Start(); err != nil { _ = stdout.Close() _ = stderr.Close() + cleanupSecret() inst.Status = domain.StatusFailed _ = s.Store.UpsertInstance(ctx, inst) startErr = err @@ -287,12 +292,13 @@ func (s *Supervisor) startWithAttempt(ctx context.Context, instanceID string, re inst.Status = domain.StatusRunning if err := s.Store.UpsertInstance(ctx, inst); err != nil { _ = cmd.Process.Kill() + cleanupSecret() startErr = err return err } logger.Info("instance_started", "instance_id", instanceID, "pid", pid, "listen_addr", addr) s.mu.Lock() - state := &processState{cmd: cmd, done: make(chan struct{}), attempt: restartAttempt, generation: generation} + state := &processState{cmd: cmd, done: make(chan struct{}), attempt: restartAttempt, generation: generation, cleanup: cleanupSecret} s.procs[instanceID] = state s.mu.Unlock() if err := waitHealth(ctx, addr, 5*time.Second); err != nil { @@ -319,6 +325,9 @@ func (s *Supervisor) cleanupFailedStart(instanceID string, state *processState, _ = state.cmd.Process.Kill() _ = state.cmd.Wait() } + if state.cleanup != nil { + state.cleanup() + } close(state.done) _ = stdout.Close() _ = stderr.Close() @@ -558,6 +567,9 @@ func (s *Supervisor) wait(instanceID string, state *processState, stdout, stderr err := state.cmd.Wait() _ = stdout.Close() _ = stderr.Close() + if state.cleanup != nil { + state.cleanup() + } close(state.done) s.mu.Lock() current := s.procs[instanceID] diff --git a/services/zhizhangyi__mbs/config.schema.json b/services/zhizhangyi__mbs/config.schema.json index b79b6fa4..ad7646c5 100644 --- a/services/zhizhangyi__mbs/config.schema.json +++ b/services/zhizhangyi__mbs/config.schema.json @@ -5,8 +5,7 @@ "properties": { "endpoint": { "type": "string", - "default": "https://101.37.25.245:9074", - "description": "MBS REST API base URL. Default: https://101.37.25.245:9074" + "description": "MBS REST API base URL. Example: https://mbs.example.com:9074" }, "baseUrl": { "type": "string", @@ -14,8 +13,8 @@ }, "skipTlsVerify": { "type": "boolean", - "default": true, - "description": "Skip TLS certificate verification (MBS uses self-signed certs)." + "default": false, + "description": "Skip TLS certificate verification. Enable only when the target MBS endpoint uses a trusted self-signed certificate." }, "timeoutMs": { "type": "integer", diff --git a/services/zhizhangyi__mbs/secret.schema.json b/services/zhizhangyi__mbs/secret.schema.json index 7513b1da..8e621f3c 100644 --- a/services/zhizhangyi__mbs/secret.schema.json +++ b/services/zhizhangyi__mbs/secret.schema.json @@ -5,18 +5,15 @@ "properties": { "appkey": { "type": "string", - "default": "jiekoudiaoyongappkey", - "description": "MBS third-party access appkey. Default: jiekoudiaoyongappkey" + "description": "MBS third-party access appkey." }, "secretkey": { "type": "string", - "default": "jiekoudiaoyongsecretkey", "description": "MBS third-party access secretkey paired with appkey." }, "orgCode": { "type": "string", - "default": "test", - "description": "Default organization code (orgCode) for MBS tenant. Default: test" + "description": "Organization code (orgCode) for MBS tenant." } } } diff --git a/services/zhizhangyi__mbs/testdata/test_4apis.mjs b/services/zhizhangyi__mbs/testdata/test_4apis.mjs index 4b970bb9..1a7fe25f 100644 --- a/services/zhizhangyi__mbs/testdata/test_4apis.mjs +++ b/services/zhizhangyi__mbs/testdata/test_4apis.mjs @@ -3,11 +3,18 @@ import crypto from 'node:crypto'; import https from 'node:https'; const C = { - baseUrl: 'https://101.37.25.245:9074', - orgCode: 'test', - appkey: 'jiekoudiaoyongappkey', - secretkey: 'jiekoudiaoyongsecretkey', + baseUrl: process.env.MBS_BASE_URL || 'https://127.0.0.1:9074', + orgCode: process.env.MBS_ORG_CODE || '', + appkey: process.env.MBS_APPKEY || '', + secretkey: process.env.MBS_SECRETKEY || '', }; +const requireConfig = () => { + const missing = Object.entries(C).filter(([, value]) => !value).map(([key]) => key); + if (missing.length > 0) { + throw new Error(`Missing MBS test config: ${missing.join(', ')}. Set MBS_BASE_URL, MBS_ORG_CODE, MBS_APPKEY and MBS_SECRETKEY.`); + } +}; +requireConfig(); const BASE = `${C.baseUrl}/uusafe/mos/thirdaccess/rest/opt`; const agent = new https.Agent({ rejectUnauthorized: false }); const md5 = (s) => crypto.createHash('md5').update(s, 'utf8').digest('hex'); diff --git a/services/zhizhangyi__mbs/testdata/test_getusers.mjs b/services/zhizhangyi__mbs/testdata/test_getusers.mjs index 642ff436..a1e4d0ff 100644 --- a/services/zhizhangyi__mbs/testdata/test_getusers.mjs +++ b/services/zhizhangyi__mbs/testdata/test_getusers.mjs @@ -5,10 +5,10 @@ import crypto from 'node:crypto'; import https from 'node:https'; const CONFIG = { - baseUrl: 'https://101.37.25.245:9074', - orgCode: 'test', - appkey: 'jiekoudiaoyongappkey', - secretkey: 'jiekoudiaoyongsecretkey', + baseUrl: process.env.MBS_BASE_URL || 'https://127.0.0.1:9074', + orgCode: process.env.MBS_ORG_CODE || '', + appkey: process.env.MBS_APPKEY || '', + secretkey: process.env.MBS_SECRETKEY || '', }; const md5 = (s) => crypto.createHash('md5').update(s, 'utf8').digest('hex'); @@ -20,7 +20,15 @@ const computeSign = (params) => { return md5(joined + CONFIG.secretkey); }; +const requireConfig = () => { + const missing = Object.entries(CONFIG).filter(([, value]) => !value).map(([key]) => key); + if (missing.length > 0) { + throw new Error(`Missing MBS test config: ${missing.join(', ')}. Set MBS_BASE_URL, MBS_ORG_CODE, MBS_APPKEY and MBS_SECRETKEY.`); + } +}; + const testGetUsers = async () => { + requireConfig(); const { baseUrl, orgCode, appkey } = CONFIG; const index = 0; diff --git a/services/zhizhangyi__mbs/testdata/test_mbs_client.go b/services/zhizhangyi__mbs/testdata/test_mbs_client.go index 2bdb60b0..19bdb531 100644 --- a/services/zhizhangyi__mbs/testdata/test_mbs_client.go +++ b/services/zhizhangyi__mbs/testdata/test_mbs_client.go @@ -16,7 +16,12 @@ import ( "time" ) -var cfg = struct{ B, O, A, S string }{"https://101.37.25.245:9074", "test", "jiekoudiaoyongappkey", "jiekoudiaoyongsecretkey"} +var cfg = struct{ B, O, A, S string }{ + B: envOr("MBS_BASE_URL", "https://127.0.0.1:9074"), + O: os.Getenv("MBS_ORG_CODE"), + A: os.Getenv("MBS_APPKEY"), + S: os.Getenv("MBS_SECRETKEY"), +} const apiBase = "/uusafe/mos/thirdaccess/rest/opt" const uid1 = "1691979294102310912" // test1 @@ -24,6 +29,26 @@ const uid2 = "1691979421122613248" // test2 var pass, failn, skipn int +func envOr(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + return fallback +} + +func requireConfig() { + var missing []string + for key, value := range map[string]string{"MBS_ORG_CODE": cfg.O, "MBS_APPKEY": cfg.A, "MBS_SECRETKEY": cfg.S} { + if value == "" { + missing = append(missing, key) + } + } + if len(missing) > 0 { + fmt.Fprintf(os.Stderr, "missing MBS test config: %s; set MBS_BASE_URL, MBS_ORG_CODE, MBS_APPKEY and MBS_SECRETKEY\n", strings.Join(missing, ", ")) + os.Exit(2) + } +} + func md5s(s string) string { h := md5.Sum([]byte(s)); return fmt.Sprintf("%x", h) } func sign(p ...string) string { return md5s(strings.Join(p, "") + cfg.S) } func gf(m map[string]interface{}, k string) float64 { f, _ := m[k].(float64); return f } @@ -231,6 +256,7 @@ func tForceOffline() { hdr("6.2.11 ForceOffline"); sk("test1无在线会话") } func tImportUser() { hdr("6.2.12 ImportUser"); sk("需上传文件") } func main() { + requireConfig() tn := flag.String("test", "all", "test name: all|scenarios|apis|scenario1|getUsers|...") flag.Parse() all := map[string]func(){ diff --git a/services/zhizhangyi__mbs/testdata/test_scenarios.mjs b/services/zhizhangyi__mbs/testdata/test_scenarios.mjs index 42b2a52c..e01affb8 100644 --- a/services/zhizhangyi__mbs/testdata/test_scenarios.mjs +++ b/services/zhizhangyi__mbs/testdata/test_scenarios.mjs @@ -3,11 +3,18 @@ import crypto from 'node:crypto'; import https from 'node:https'; const C = { - baseUrl: 'https://101.37.25.245:9074', - orgCode: 'test', - appkey: 'jiekoudiaoyongappkey', - secretkey: 'jiekoudiaoyongsecretkey', + baseUrl: process.env.MBS_BASE_URL || 'https://127.0.0.1:9074', + orgCode: process.env.MBS_ORG_CODE || '', + appkey: process.env.MBS_APPKEY || '', + secretkey: process.env.MBS_SECRETKEY || '', }; +const requireConfig = () => { + const missing = Object.entries(C).filter(([, value]) => !value).map(([key]) => key); + if (missing.length > 0) { + throw new Error(`Missing MBS test config: ${missing.join(', ')}. Set MBS_BASE_URL, MBS_ORG_CODE, MBS_APPKEY and MBS_SECRETKEY.`); + } +}; +requireConfig(); const BASE = `${C.baseUrl}/uusafe/mos/thirdaccess/rest/opt`; const agent = new https.Agent({ rejectUnauthorized: false }); const md5 = (s) => crypto.createHash('md5').update(s, 'utf8').digest('hex');