| package dynamic |
| |
| // Marshalling and unmarshalling of dynamic messages to/from proto's standard text format |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "math" |
| "reflect" |
| "sort" |
| "strconv" |
| "strings" |
| "text/scanner" |
| "unicode" |
| |
| "github.com/golang/protobuf/proto" |
| "github.com/golang/protobuf/protoc-gen-go/descriptor" |
| |
| "github.com/jhump/protoreflect/codec" |
| "github.com/jhump/protoreflect/desc" |
| ) |
| |
| // MarshalText serializes this message to bytes in the standard text format, |
| // returning an error if the operation fails. The resulting bytes will be a |
| // valid UTF8 string. |
| // |
| // This method uses a compact form: no newlines, and spaces between field |
| // identifiers and values are elided. |
| func (m *Message) MarshalText() ([]byte, error) { |
| var b indentBuffer |
| b.indentCount = -1 // no indentation |
| if err := m.marshalText(&b); err != nil { |
| return nil, err |
| } |
| return b.Bytes(), nil |
| } |
| |
| // MarshalTextIndent serializes this message to bytes in the standard text |
| // format, returning an error if the operation fails. The resulting bytes will |
| // be a valid UTF8 string. |
| // |
| // This method uses a "pretty-printed" form, with each field on its own line and |
| // spaces between field identifiers and values. |
| func (m *Message) MarshalTextIndent() ([]byte, error) { |
| var b indentBuffer |
| b.indent = " " // TODO: option for indent? |
| if err := m.marshalText(&b); err != nil { |
| return nil, err |
| } |
| return b.Bytes(), nil |
| } |
| |
| func (m *Message) marshalText(b *indentBuffer) error { |
| // TODO: option for emitting extended Any format? |
| first := true |
| // first the known fields |
| for _, tag := range m.knownFieldTags() { |
| itag := int32(tag) |
| v := m.values[itag] |
| fd := m.FindFieldDescriptor(itag) |
| if fd.IsMap() { |
| md := fd.GetMessageType() |
| kfd := md.FindFieldByNumber(1) |
| vfd := md.FindFieldByNumber(2) |
| mp := v.(map[interface{}]interface{}) |
| keys := make([]interface{}, 0, len(mp)) |
| for k := range mp { |
| keys = append(keys, k) |
| } |
| sort.Sort(sortable(keys)) |
| for _, mk := range keys { |
| mv := mp[mk] |
| err := b.maybeNext(&first) |
| if err != nil { |
| return err |
| } |
| err = marshalKnownFieldMapEntryText(b, fd, kfd, mk, vfd, mv) |
| if err != nil { |
| return err |
| } |
| } |
| } else if fd.IsRepeated() { |
| sl := v.([]interface{}) |
| for _, slv := range sl { |
| err := b.maybeNext(&first) |
| if err != nil { |
| return err |
| } |
| err = marshalKnownFieldText(b, fd, slv) |
| if err != nil { |
| return err |
| } |
| } |
| } else { |
| err := b.maybeNext(&first) |
| if err != nil { |
| return err |
| } |
| err = marshalKnownFieldText(b, fd, v) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| // then the unknown fields |
| for _, tag := range m.unknownFieldTags() { |
| itag := int32(tag) |
| ufs := m.unknownFields[itag] |
| for _, uf := range ufs { |
| err := b.maybeNext(&first) |
| if err != nil { |
| return err |
| } |
| _, err = fmt.Fprintf(b, "%d", tag) |
| if err != nil { |
| return err |
| } |
| if uf.Encoding == proto.WireStartGroup { |
| err = b.WriteByte('{') |
| if err != nil { |
| return err |
| } |
| err = b.start() |
| if err != nil { |
| return err |
| } |
| in := codec.NewBuffer(uf.Contents) |
| err = marshalUnknownGroupText(b, in, true) |
| if err != nil { |
| return err |
| } |
| err = b.end() |
| if err != nil { |
| return err |
| } |
| err = b.WriteByte('}') |
| if err != nil { |
| return err |
| } |
| } else { |
| err = b.sep() |
| if err != nil { |
| return err |
| } |
| if uf.Encoding == proto.WireBytes { |
| err = writeString(b, string(uf.Contents)) |
| if err != nil { |
| return err |
| } |
| } else { |
| _, err = b.WriteString(strconv.FormatUint(uf.Value, 10)) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| } |
| return nil |
| } |
| |
| func marshalKnownFieldMapEntryText(b *indentBuffer, fd *desc.FieldDescriptor, kfd *desc.FieldDescriptor, mk interface{}, vfd *desc.FieldDescriptor, mv interface{}) error { |
| var name string |
| if fd.IsExtension() { |
| name = fmt.Sprintf("[%s]", fd.GetFullyQualifiedName()) |
| } else { |
| name = fd.GetName() |
| } |
| _, err := b.WriteString(name) |
| if err != nil { |
| return err |
| } |
| err = b.sep() |
| if err != nil { |
| return err |
| } |
| |
| err = b.WriteByte('<') |
| if err != nil { |
| return err |
| } |
| err = b.start() |
| if err != nil { |
| return err |
| } |
| |
| err = marshalKnownFieldText(b, kfd, mk) |
| if err != nil { |
| return err |
| } |
| err = b.next() |
| if err != nil { |
| return err |
| } |
| err = marshalKnownFieldText(b, vfd, mv) |
| if err != nil { |
| return err |
| } |
| |
| err = b.end() |
| if err != nil { |
| return err |
| } |
| return b.WriteByte('>') |
| } |
| |
| func marshalKnownFieldText(b *indentBuffer, fd *desc.FieldDescriptor, v interface{}) error { |
| group := fd.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP |
| if group { |
| var name string |
| if fd.IsExtension() { |
| name = fmt.Sprintf("[%s]", fd.GetMessageType().GetFullyQualifiedName()) |
| } else { |
| name = fd.GetMessageType().GetName() |
| } |
| _, err := b.WriteString(name) |
| if err != nil { |
| return err |
| } |
| } else { |
| var name string |
| if fd.IsExtension() { |
| name = fmt.Sprintf("[%s]", fd.GetFullyQualifiedName()) |
| } else { |
| name = fd.GetName() |
| } |
| _, err := b.WriteString(name) |
| if err != nil { |
| return err |
| } |
| err = b.sep() |
| if err != nil { |
| return err |
| } |
| } |
| rv := reflect.ValueOf(v) |
| switch rv.Kind() { |
| case reflect.Int32, reflect.Int64: |
| ed := fd.GetEnumType() |
| if ed != nil { |
| n := int32(rv.Int()) |
| vd := ed.FindValueByNumber(n) |
| if vd == nil { |
| _, err := b.WriteString(strconv.FormatInt(rv.Int(), 10)) |
| return err |
| } else { |
| _, err := b.WriteString(vd.GetName()) |
| return err |
| } |
| } else { |
| _, err := b.WriteString(strconv.FormatInt(rv.Int(), 10)) |
| return err |
| } |
| case reflect.Uint32, reflect.Uint64: |
| _, err := b.WriteString(strconv.FormatUint(rv.Uint(), 10)) |
| return err |
| case reflect.Float32, reflect.Float64: |
| f := rv.Float() |
| var str string |
| if math.IsNaN(f) { |
| str = "nan" |
| } else if math.IsInf(f, 1) { |
| str = "inf" |
| } else if math.IsInf(f, -1) { |
| str = "-inf" |
| } else { |
| var bits int |
| if rv.Kind() == reflect.Float32 { |
| bits = 32 |
| } else { |
| bits = 64 |
| } |
| str = strconv.FormatFloat(rv.Float(), 'g', -1, bits) |
| } |
| _, err := b.WriteString(str) |
| return err |
| case reflect.Bool: |
| _, err := b.WriteString(strconv.FormatBool(rv.Bool())) |
| return err |
| case reflect.Slice: |
| return writeString(b, string(rv.Bytes())) |
| case reflect.String: |
| return writeString(b, rv.String()) |
| default: |
| var err error |
| if group { |
| err = b.WriteByte('{') |
| } else { |
| err = b.WriteByte('<') |
| } |
| if err != nil { |
| return err |
| } |
| err = b.start() |
| if err != nil { |
| return err |
| } |
| // must be a message |
| if dm, ok := v.(*Message); ok { |
| err = dm.marshalText(b) |
| if err != nil { |
| return err |
| } |
| } else { |
| err = proto.CompactText(b, v.(proto.Message)) |
| if err != nil { |
| return err |
| } |
| } |
| err = b.end() |
| if err != nil { |
| return err |
| } |
| if group { |
| return b.WriteByte('}') |
| } else { |
| return b.WriteByte('>') |
| } |
| } |
| } |
| |
| // writeString writes a string in the protocol buffer text format. |
| // It is similar to strconv.Quote except we don't use Go escape sequences, |
| // we treat the string as a byte sequence, and we use octal escapes. |
| // These differences are to maintain interoperability with the other |
| // languages' implementations of the text format. |
| func writeString(b *indentBuffer, s string) error { |
| // use WriteByte here to get any needed indent |
| if err := b.WriteByte('"'); err != nil { |
| return err |
| } |
| // Loop over the bytes, not the runes. |
| for i := 0; i < len(s); i++ { |
| var err error |
| // Divergence from C++: we don't escape apostrophes. |
| // There's no need to escape them, and the C++ parser |
| // copes with a naked apostrophe. |
| switch c := s[i]; c { |
| case '\n': |
| _, err = b.WriteString("\\n") |
| case '\r': |
| _, err = b.WriteString("\\r") |
| case '\t': |
| _, err = b.WriteString("\\t") |
| case '"': |
| _, err = b.WriteString("\\") |
| case '\\': |
| _, err = b.WriteString("\\\\") |
| default: |
| if c >= 0x20 && c < 0x7f { |
| err = b.WriteByte(c) |
| } else { |
| _, err = fmt.Fprintf(b, "\\%03o", c) |
| } |
| } |
| if err != nil { |
| return err |
| } |
| } |
| return b.WriteByte('"') |
| } |
| |
| func marshalUnknownGroupText(b *indentBuffer, in *codec.Buffer, topLevel bool) error { |
| first := true |
| for { |
| if in.EOF() { |
| if topLevel { |
| return nil |
| } |
| // this is a nested message: we are expecting an end-group tag, not EOF! |
| return io.ErrUnexpectedEOF |
| } |
| tag, wireType, err := in.DecodeTagAndWireType() |
| if err != nil { |
| return err |
| } |
| if wireType == proto.WireEndGroup { |
| return nil |
| } |
| err = b.maybeNext(&first) |
| if err != nil { |
| return err |
| } |
| _, err = fmt.Fprintf(b, "%d", tag) |
| if err != nil { |
| return err |
| } |
| if wireType == proto.WireStartGroup { |
| err = b.WriteByte('{') |
| if err != nil { |
| return err |
| } |
| err = b.start() |
| if err != nil { |
| return err |
| } |
| err = marshalUnknownGroupText(b, in, false) |
| if err != nil { |
| return err |
| } |
| err = b.end() |
| if err != nil { |
| return err |
| } |
| err = b.WriteByte('}') |
| if err != nil { |
| return err |
| } |
| continue |
| } else { |
| err = b.sep() |
| if err != nil { |
| return err |
| } |
| if wireType == proto.WireBytes { |
| contents, err := in.DecodeRawBytes(false) |
| if err != nil { |
| return err |
| } |
| err = writeString(b, string(contents)) |
| if err != nil { |
| return err |
| } |
| } else { |
| var v uint64 |
| switch wireType { |
| case proto.WireVarint: |
| v, err = in.DecodeVarint() |
| case proto.WireFixed32: |
| v, err = in.DecodeFixed32() |
| case proto.WireFixed64: |
| v, err = in.DecodeFixed64() |
| default: |
| return proto.ErrInternalBadWireType |
| } |
| if err != nil { |
| return err |
| } |
| _, err = b.WriteString(strconv.FormatUint(v, 10)) |
| if err != nil { |
| return err |
| } |
| } |
| } |
| } |
| } |
| |
| // UnmarshalText de-serializes the message that is present, in text format, in |
| // the given bytes into this message. It first resets the current message. It |
| // returns an error if the given bytes do not contain a valid encoding of this |
| // message type in the standard text format |
| func (m *Message) UnmarshalText(text []byte) error { |
| m.Reset() |
| if err := m.UnmarshalMergeText(text); err != nil { |
| return err |
| } |
| return m.Validate() |
| } |
| |
| // UnmarshalMergeText de-serializes the message that is present, in text format, |
| // in the given bytes into this message. Unlike UnmarshalText, it does not first |
| // reset the message, instead merging the data in the given bytes into the |
| // existing data in this message. |
| func (m *Message) UnmarshalMergeText(text []byte) error { |
| return m.unmarshalText(newReader(text), tokenEOF) |
| } |
| |
| func (m *Message) unmarshalText(tr *txtReader, end tokenType) error { |
| for { |
| tok := tr.next() |
| if tok.tokTyp == end { |
| return nil |
| } |
| if tok.tokTyp == tokenEOF { |
| return io.ErrUnexpectedEOF |
| } |
| var fd *desc.FieldDescriptor |
| var extendedAnyType *desc.MessageDescriptor |
| if tok.tokTyp == tokenInt { |
| // tag number (indicates unknown field) |
| tag, err := strconv.ParseInt(tok.val.(string), 10, 32) |
| if err != nil { |
| return err |
| } |
| itag := int32(tag) |
| fd = m.FindFieldDescriptor(itag) |
| if fd == nil { |
| // can't parse the value w/out field descriptor, so skip it |
| tok = tr.next() |
| if tok.tokTyp == tokenEOF { |
| return io.ErrUnexpectedEOF |
| } else if tok.tokTyp == tokenOpenBrace { |
| if err := skipMessageText(tr, true); err != nil { |
| return err |
| } |
| } else if tok.tokTyp == tokenColon { |
| if err := skipFieldValueText(tr); err != nil { |
| return err |
| } |
| } else { |
| return textError(tok, "Expecting a colon ':' or brace '{'; instead got %q", tok.txt) |
| } |
| tok = tr.peek() |
| if tok.tokTyp.IsSep() { |
| tr.next() // consume separator |
| } |
| continue |
| } |
| } else { |
| fieldName, err := unmarshalFieldNameText(tr, tok) |
| if err != nil { |
| return err |
| } |
| fd = m.FindFieldDescriptorByName(fieldName) |
| if fd == nil { |
| // See if it's a group name |
| for _, field := range m.md.GetFields() { |
| if field.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP && field.GetMessageType().GetName() == fieldName { |
| fd = field |
| break |
| } |
| } |
| if fd == nil { |
| // maybe this is an extended Any |
| if m.md.GetFullyQualifiedName() == "google.protobuf.Any" && fieldName[0] == '[' && strings.Contains(fieldName, "/") { |
| // strip surrounding "[" and "]" and extract type name from URL |
| typeUrl := fieldName[1 : len(fieldName)-1] |
| mname := typeUrl |
| if slash := strings.LastIndex(mname, "/"); slash >= 0 { |
| mname = mname[slash+1:] |
| } |
| // TODO: add a way to weave an AnyResolver to this point |
| extendedAnyType = findMessageDescriptor(mname, m.md.GetFile()) |
| if extendedAnyType == nil { |
| return textError(tok, "could not parse Any with unknown type URL %q", fieldName) |
| } |
| // field 1 is "type_url" |
| typeUrlField := m.md.FindFieldByNumber(1) |
| if err := m.TrySetField(typeUrlField, typeUrl); err != nil { |
| return err |
| } |
| } else { |
| // TODO: add a flag to just ignore unrecognized field names |
| return textError(tok, "%q is not a recognized field name of %q", fieldName, m.md.GetFullyQualifiedName()) |
| } |
| } |
| } |
| } |
| tok = tr.next() |
| if tok.tokTyp == tokenEOF { |
| return io.ErrUnexpectedEOF |
| } |
| if extendedAnyType != nil { |
| // consume optional colon; make sure this is a "start message" token |
| if tok.tokTyp == tokenColon { |
| tok = tr.next() |
| if tok.tokTyp == tokenEOF { |
| return io.ErrUnexpectedEOF |
| } |
| } |
| if tok.tokTyp.EndToken() == tokenError { |
| return textError(tok, "Expecting a '<' or '{'; instead got %q", tok.txt) |
| } |
| |
| // TODO: use mf.NewMessage and, if not a dynamic message, use proto.UnmarshalText to unmarshal it |
| g := m.mf.NewDynamicMessage(extendedAnyType) |
| if err := g.unmarshalText(tr, tok.tokTyp.EndToken()); err != nil { |
| return err |
| } |
| // now we marshal the message to bytes and store in the Any |
| b, err := g.Marshal() |
| if err != nil { |
| return err |
| } |
| // field 2 is "value" |
| anyValueField := m.md.FindFieldByNumber(2) |
| if err := m.TrySetField(anyValueField, b); err != nil { |
| return err |
| } |
| |
| } else if (fd.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP || |
| fd.GetType() == descriptor.FieldDescriptorProto_TYPE_MESSAGE) && |
| tok.tokTyp.EndToken() != tokenError { |
| |
| // TODO: use mf.NewMessage and, if not a dynamic message, use proto.UnmarshalText to unmarshal it |
| g := m.mf.NewDynamicMessage(fd.GetMessageType()) |
| if err := g.unmarshalText(tr, tok.tokTyp.EndToken()); err != nil { |
| return err |
| } |
| if fd.IsRepeated() { |
| if err := m.TryAddRepeatedField(fd, g); err != nil { |
| return err |
| } |
| } else { |
| if err := m.TrySetField(fd, g); err != nil { |
| return err |
| } |
| } |
| } else { |
| if tok.tokTyp != tokenColon { |
| return textError(tok, "Expecting a colon ':'; instead got %q", tok.txt) |
| } |
| if err := m.unmarshalFieldValueText(fd, tr); err != nil { |
| return err |
| } |
| } |
| tok = tr.peek() |
| if tok.tokTyp.IsSep() { |
| tr.next() // consume separator |
| } |
| } |
| } |
| func findMessageDescriptor(name string, fd *desc.FileDescriptor) *desc.MessageDescriptor { |
| md := findMessageInTransitiveDeps(name, fd, map[*desc.FileDescriptor]struct{}{}) |
| if md == nil { |
| // couldn't find it; see if we have this message linked in |
| md, _ = desc.LoadMessageDescriptor(name) |
| } |
| return md |
| } |
| |
| func findMessageInTransitiveDeps(name string, fd *desc.FileDescriptor, seen map[*desc.FileDescriptor]struct{}) *desc.MessageDescriptor { |
| if _, ok := seen[fd]; ok { |
| // already checked this file |
| return nil |
| } |
| seen[fd] = struct{}{} |
| md := fd.FindMessage(name) |
| if md != nil { |
| return md |
| } |
| // not in this file so recursively search its deps |
| for _, dep := range fd.GetDependencies() { |
| md = findMessageInTransitiveDeps(name, dep, seen) |
| if md != nil { |
| return md |
| } |
| } |
| // couldn't find it |
| return nil |
| } |
| |
| func textError(tok *token, format string, args ...interface{}) error { |
| var msg string |
| if tok.tokTyp == tokenError { |
| msg = tok.val.(error).Error() |
| } else { |
| msg = fmt.Sprintf(format, args...) |
| } |
| return fmt.Errorf("line %d, col %d: %s", tok.pos.Line, tok.pos.Column, msg) |
| } |
| |
| type setFunction func(*Message, *desc.FieldDescriptor, interface{}) error |
| |
| func (m *Message) unmarshalFieldValueText(fd *desc.FieldDescriptor, tr *txtReader) error { |
| var set setFunction |
| if fd.IsRepeated() { |
| set = (*Message).addRepeatedField |
| } else { |
| set = mergeField |
| } |
| tok := tr.peek() |
| if tok.tokTyp == tokenOpenBracket { |
| tr.next() // consume tok |
| for { |
| if err := m.unmarshalFieldElementText(fd, tr, set); err != nil { |
| return err |
| } |
| tok = tr.peek() |
| if tok.tokTyp == tokenCloseBracket { |
| tr.next() // consume tok |
| return nil |
| } else if tok.tokTyp.IsSep() { |
| tr.next() // consume separator |
| } |
| } |
| } |
| return m.unmarshalFieldElementText(fd, tr, set) |
| } |
| |
| func (m *Message) unmarshalFieldElementText(fd *desc.FieldDescriptor, tr *txtReader, set setFunction) error { |
| tok := tr.next() |
| if tok.tokTyp == tokenEOF { |
| return io.ErrUnexpectedEOF |
| } |
| |
| var expected string |
| switch fd.GetType() { |
| case descriptor.FieldDescriptorProto_TYPE_BOOL: |
| if tok.tokTyp == tokenIdent { |
| if tok.val.(string) == "true" { |
| return set(m, fd, true) |
| } else if tok.val.(string) == "false" { |
| return set(m, fd, false) |
| } |
| } |
| expected = "boolean value" |
| case descriptor.FieldDescriptorProto_TYPE_BYTES: |
| if tok.tokTyp == tokenString { |
| return set(m, fd, []byte(tok.val.(string))) |
| } |
| expected = "bytes string value" |
| case descriptor.FieldDescriptorProto_TYPE_STRING: |
| if tok.tokTyp == tokenString { |
| return set(m, fd, tok.val) |
| } |
| expected = "string value" |
| case descriptor.FieldDescriptorProto_TYPE_FLOAT: |
| switch tok.tokTyp { |
| case tokenFloat: |
| return set(m, fd, float32(tok.val.(float64))) |
| case tokenInt: |
| if f, err := strconv.ParseFloat(tok.val.(string), 32); err != nil { |
| return err |
| } else { |
| return set(m, fd, float32(f)) |
| } |
| case tokenIdent: |
| ident := strings.ToLower(tok.val.(string)) |
| if ident == "inf" { |
| return set(m, fd, float32(math.Inf(1))) |
| } else if ident == "nan" { |
| return set(m, fd, float32(math.NaN())) |
| } |
| case tokenMinus: |
| peeked := tr.peek() |
| if peeked.tokTyp == tokenIdent { |
| ident := strings.ToLower(peeked.val.(string)) |
| if ident == "inf" { |
| tr.next() // consume peeked token |
| return set(m, fd, float32(math.Inf(-1))) |
| } |
| } |
| } |
| expected = "float value" |
| case descriptor.FieldDescriptorProto_TYPE_DOUBLE: |
| switch tok.tokTyp { |
| case tokenFloat: |
| return set(m, fd, tok.val) |
| case tokenInt: |
| if f, err := strconv.ParseFloat(tok.val.(string), 64); err != nil { |
| return err |
| } else { |
| return set(m, fd, f) |
| } |
| case tokenIdent: |
| ident := strings.ToLower(tok.val.(string)) |
| if ident == "inf" { |
| return set(m, fd, math.Inf(1)) |
| } else if ident == "nan" { |
| return set(m, fd, math.NaN()) |
| } |
| case tokenMinus: |
| peeked := tr.peek() |
| if peeked.tokTyp == tokenIdent { |
| ident := strings.ToLower(peeked.val.(string)) |
| if ident == "inf" { |
| tr.next() // consume peeked token |
| return set(m, fd, math.Inf(-1)) |
| } |
| } |
| } |
| expected = "float value" |
| case descriptor.FieldDescriptorProto_TYPE_INT32, |
| descriptor.FieldDescriptorProto_TYPE_SINT32, |
| descriptor.FieldDescriptorProto_TYPE_SFIXED32: |
| if tok.tokTyp == tokenInt { |
| if i, err := strconv.ParseInt(tok.val.(string), 10, 32); err != nil { |
| return err |
| } else { |
| return set(m, fd, int32(i)) |
| } |
| } |
| expected = "int value" |
| case descriptor.FieldDescriptorProto_TYPE_INT64, |
| descriptor.FieldDescriptorProto_TYPE_SINT64, |
| descriptor.FieldDescriptorProto_TYPE_SFIXED64: |
| if tok.tokTyp == tokenInt { |
| if i, err := strconv.ParseInt(tok.val.(string), 10, 64); err != nil { |
| return err |
| } else { |
| return set(m, fd, i) |
| } |
| } |
| expected = "int value" |
| case descriptor.FieldDescriptorProto_TYPE_UINT32, |
| descriptor.FieldDescriptorProto_TYPE_FIXED32: |
| if tok.tokTyp == tokenInt { |
| if i, err := strconv.ParseUint(tok.val.(string), 10, 32); err != nil { |
| return err |
| } else { |
| return set(m, fd, uint32(i)) |
| } |
| } |
| expected = "unsigned int value" |
| case descriptor.FieldDescriptorProto_TYPE_UINT64, |
| descriptor.FieldDescriptorProto_TYPE_FIXED64: |
| if tok.tokTyp == tokenInt { |
| if i, err := strconv.ParseUint(tok.val.(string), 10, 64); err != nil { |
| return err |
| } else { |
| return set(m, fd, i) |
| } |
| } |
| expected = "unsigned int value" |
| case descriptor.FieldDescriptorProto_TYPE_ENUM: |
| if tok.tokTyp == tokenIdent { |
| // TODO: add a flag to just ignore unrecognized enum value names? |
| vd := fd.GetEnumType().FindValueByName(tok.val.(string)) |
| if vd != nil { |
| return set(m, fd, vd.GetNumber()) |
| } |
| } else if tok.tokTyp == tokenInt { |
| if i, err := strconv.ParseInt(tok.val.(string), 10, 32); err != nil { |
| return err |
| } else { |
| return set(m, fd, int32(i)) |
| } |
| } |
| expected = fmt.Sprintf("enum %s value", fd.GetEnumType().GetFullyQualifiedName()) |
| case descriptor.FieldDescriptorProto_TYPE_MESSAGE, |
| descriptor.FieldDescriptorProto_TYPE_GROUP: |
| |
| endTok := tok.tokTyp.EndToken() |
| if endTok != tokenError { |
| dm := m.mf.NewDynamicMessage(fd.GetMessageType()) |
| if err := dm.unmarshalText(tr, endTok); err != nil { |
| return err |
| } |
| // TODO: ideally we would use mf.NewMessage and, if not a dynamic message, use |
| // proto package to unmarshal it. But the text parser isn't particularly amenable |
| // to that, so we instead convert a dynamic message to a generated one if the |
| // known-type registry knows about the generated type... |
| var ktr *KnownTypeRegistry |
| if m.mf != nil { |
| ktr = m.mf.ktr |
| } |
| pm := ktr.CreateIfKnown(fd.GetMessageType().GetFullyQualifiedName()) |
| if pm != nil { |
| if err := dm.ConvertTo(pm); err != nil { |
| return set(m, fd, pm) |
| } |
| } |
| return set(m, fd, dm) |
| } |
| expected = fmt.Sprintf("message %s value", fd.GetMessageType().GetFullyQualifiedName()) |
| default: |
| return fmt.Errorf("field %q of message %q has unrecognized type: %v", fd.GetFullyQualifiedName(), m.md.GetFullyQualifiedName(), fd.GetType()) |
| } |
| |
| // if we get here, token was wrong type; create error message |
| var article string |
| if strings.Contains("aieou", expected[0:1]) { |
| article = "an" |
| } else { |
| article = "a" |
| } |
| return textError(tok, "Expecting %s %s; got %q", article, expected, tok.txt) |
| } |
| |
| func unmarshalFieldNameText(tr *txtReader, tok *token) (string, error) { |
| if tok.tokTyp == tokenOpenBracket || tok.tokTyp == tokenOpenParen { |
| // extension name |
| var closeType tokenType |
| var closeChar string |
| if tok.tokTyp == tokenOpenBracket { |
| closeType = tokenCloseBracket |
| closeChar = "close bracket ']'" |
| } else { |
| closeType = tokenCloseParen |
| closeChar = "close paren ')'" |
| } |
| // must be followed by an identifier |
| idents := make([]string, 0, 1) |
| for { |
| tok = tr.next() |
| if tok.tokTyp == tokenEOF { |
| return "", io.ErrUnexpectedEOF |
| } else if tok.tokTyp != tokenIdent { |
| return "", textError(tok, "Expecting an identifier; instead got %q", tok.txt) |
| } |
| idents = append(idents, tok.val.(string)) |
| // and then close bracket/paren, or "/" to keep adding URL elements to name |
| tok = tr.next() |
| if tok.tokTyp == tokenEOF { |
| return "", io.ErrUnexpectedEOF |
| } else if tok.tokTyp == closeType { |
| break |
| } else if tok.tokTyp != tokenSlash { |
| return "", textError(tok, "Expecting a %s; instead got %q", closeChar, tok.txt) |
| } |
| } |
| return "[" + strings.Join(idents, "/") + "]", nil |
| } else if tok.tokTyp == tokenIdent { |
| // normal field name |
| return tok.val.(string), nil |
| } else { |
| return "", textError(tok, "Expecting an identifier or tag number; instead got %q", tok.txt) |
| } |
| } |
| |
| func skipFieldNameText(tr *txtReader) error { |
| tok := tr.next() |
| if tok.tokTyp == tokenEOF { |
| return io.ErrUnexpectedEOF |
| } else if tok.tokTyp == tokenInt || tok.tokTyp == tokenIdent { |
| return nil |
| } else { |
| _, err := unmarshalFieldNameText(tr, tok) |
| return err |
| } |
| } |
| |
| func skipFieldValueText(tr *txtReader) error { |
| tok := tr.peek() |
| if tok.tokTyp == tokenOpenBracket { |
| tr.next() // consume tok |
| for { |
| if err := skipFieldElementText(tr); err != nil { |
| return err |
| } |
| tok = tr.peek() |
| if tok.tokTyp == tokenCloseBracket { |
| tr.next() // consume tok |
| return nil |
| } else if tok.tokTyp.IsSep() { |
| tr.next() // consume separator |
| } |
| |
| } |
| } |
| return skipFieldElementText(tr) |
| } |
| |
| func skipFieldElementText(tr *txtReader) error { |
| tok := tr.next() |
| switch tok.tokTyp { |
| case tokenEOF: |
| return io.ErrUnexpectedEOF |
| case tokenInt, tokenFloat, tokenString, tokenIdent: |
| return nil |
| case tokenOpenAngle: |
| return skipMessageText(tr, false) |
| default: |
| return textError(tok, "Expecting an angle bracket '<' or a value; instead got %q", tok.txt) |
| } |
| } |
| |
| func skipMessageText(tr *txtReader, isGroup bool) error { |
| for { |
| tok := tr.peek() |
| if tok.tokTyp == tokenEOF { |
| return io.ErrUnexpectedEOF |
| } else if isGroup && tok.tokTyp == tokenCloseBrace { |
| return nil |
| } else if !isGroup && tok.tokTyp == tokenCloseAngle { |
| return nil |
| } |
| |
| // field name or tag |
| if err := skipFieldNameText(tr); err != nil { |
| return err |
| } |
| |
| // field value |
| tok = tr.next() |
| if tok.tokTyp == tokenEOF { |
| return io.ErrUnexpectedEOF |
| } else if tok.tokTyp == tokenOpenBrace { |
| if err := skipMessageText(tr, true); err != nil { |
| return err |
| } |
| } else if tok.tokTyp == tokenColon { |
| if err := skipFieldValueText(tr); err != nil { |
| return err |
| } |
| } else { |
| return textError(tok, "Expecting a colon ':' or brace '{'; instead got %q", tok.txt) |
| } |
| |
| tok = tr.peek() |
| if tok.tokTyp.IsSep() { |
| tr.next() // consume separator |
| } |
| } |
| } |
| |
| type tokenType int |
| |
| const ( |
| tokenError tokenType = iota |
| tokenEOF |
| tokenIdent |
| tokenString |
| tokenInt |
| tokenFloat |
| tokenColon |
| tokenComma |
| tokenSemiColon |
| tokenOpenBrace |
| tokenCloseBrace |
| tokenOpenBracket |
| tokenCloseBracket |
| tokenOpenAngle |
| tokenCloseAngle |
| tokenOpenParen |
| tokenCloseParen |
| tokenSlash |
| tokenMinus |
| ) |
| |
| func (t tokenType) IsSep() bool { |
| return t == tokenComma || t == tokenSemiColon |
| } |
| |
| func (t tokenType) EndToken() tokenType { |
| switch t { |
| case tokenOpenAngle: |
| return tokenCloseAngle |
| case tokenOpenBrace: |
| return tokenCloseBrace |
| default: |
| return tokenError |
| } |
| } |
| |
| type token struct { |
| tokTyp tokenType |
| val interface{} |
| txt string |
| pos scanner.Position |
| } |
| |
| type txtReader struct { |
| scanner scanner.Scanner |
| peeked token |
| havePeeked bool |
| } |
| |
| func newReader(text []byte) *txtReader { |
| sc := scanner.Scanner{} |
| sc.Init(bytes.NewReader(text)) |
| sc.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanChars | |
| scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments |
| // identifiers are same restrictions as Go identifiers, except we also allow dots since |
| // we accept fully-qualified names |
| sc.IsIdentRune = func(ch rune, i int) bool { |
| return ch == '_' || unicode.IsLetter(ch) || |
| (i > 0 && unicode.IsDigit(ch)) || |
| (i > 0 && ch == '.') |
| } |
| // ignore errors; we handle them if/when we see malformed tokens |
| sc.Error = func(s *scanner.Scanner, msg string) {} |
| return &txtReader{scanner: sc} |
| } |
| |
| func (p *txtReader) peek() *token { |
| if p.havePeeked { |
| return &p.peeked |
| } |
| t := p.scanner.Scan() |
| if t == scanner.EOF { |
| p.peeked.tokTyp = tokenEOF |
| p.peeked.val = nil |
| p.peeked.txt = "" |
| p.peeked.pos = p.scanner.Position |
| } else if err := p.processToken(t, p.scanner.TokenText(), p.scanner.Position); err != nil { |
| p.peeked.tokTyp = tokenError |
| p.peeked.val = err |
| } |
| p.havePeeked = true |
| return &p.peeked |
| } |
| |
| func (p *txtReader) processToken(t rune, text string, pos scanner.Position) error { |
| p.peeked.pos = pos |
| p.peeked.txt = text |
| switch t { |
| case scanner.Ident: |
| p.peeked.tokTyp = tokenIdent |
| p.peeked.val = text |
| case scanner.Int: |
| p.peeked.tokTyp = tokenInt |
| p.peeked.val = text // can't parse the number because we don't know if it's signed or unsigned |
| case scanner.Float: |
| p.peeked.tokTyp = tokenFloat |
| var err error |
| if p.peeked.val, err = strconv.ParseFloat(text, 64); err != nil { |
| return err |
| } |
| case scanner.Char, scanner.String: |
| p.peeked.tokTyp = tokenString |
| var err error |
| if p.peeked.val, err = strconv.Unquote(text); err != nil { |
| return err |
| } |
| case '-': // unary minus, for negative ints and floats |
| ch := p.scanner.Peek() |
| if ch < '0' || ch > '9' { |
| p.peeked.tokTyp = tokenMinus |
| p.peeked.val = '-' |
| } else { |
| t := p.scanner.Scan() |
| if t == scanner.EOF { |
| return io.ErrUnexpectedEOF |
| } else if t == scanner.Float { |
| p.peeked.tokTyp = tokenFloat |
| text += p.scanner.TokenText() |
| p.peeked.txt = text |
| var err error |
| if p.peeked.val, err = strconv.ParseFloat(text, 64); err != nil { |
| p.peeked.pos = p.scanner.Position |
| return err |
| } |
| } else if t == scanner.Int { |
| p.peeked.tokTyp = tokenInt |
| text += p.scanner.TokenText() |
| p.peeked.txt = text |
| p.peeked.val = text // can't parse the number because we don't know if it's signed or unsigned |
| } else { |
| p.peeked.pos = p.scanner.Position |
| return fmt.Errorf("expecting an int or float but got %q", p.scanner.TokenText()) |
| } |
| } |
| case ':': |
| p.peeked.tokTyp = tokenColon |
| p.peeked.val = ':' |
| case ',': |
| p.peeked.tokTyp = tokenComma |
| p.peeked.val = ',' |
| case ';': |
| p.peeked.tokTyp = tokenSemiColon |
| p.peeked.val = ';' |
| case '{': |
| p.peeked.tokTyp = tokenOpenBrace |
| p.peeked.val = '{' |
| case '}': |
| p.peeked.tokTyp = tokenCloseBrace |
| p.peeked.val = '}' |
| case '<': |
| p.peeked.tokTyp = tokenOpenAngle |
| p.peeked.val = '<' |
| case '>': |
| p.peeked.tokTyp = tokenCloseAngle |
| p.peeked.val = '>' |
| case '[': |
| p.peeked.tokTyp = tokenOpenBracket |
| p.peeked.val = '[' |
| case ']': |
| p.peeked.tokTyp = tokenCloseBracket |
| p.peeked.val = ']' |
| case '(': |
| p.peeked.tokTyp = tokenOpenParen |
| p.peeked.val = '(' |
| case ')': |
| p.peeked.tokTyp = tokenCloseParen |
| p.peeked.val = ')' |
| case '/': |
| // only allowed to separate URL components in expanded Any format |
| p.peeked.tokTyp = tokenSlash |
| p.peeked.val = '/' |
| default: |
| return fmt.Errorf("invalid character: %c", t) |
| } |
| return nil |
| } |
| |
| func (p *txtReader) next() *token { |
| t := p.peek() |
| if t.tokTyp != tokenEOF && t.tokTyp != tokenError { |
| p.havePeeked = false |
| } |
| return t |
| } |