@@ -28,6 +28,7 @@ import (
2828 "github.com/googleapis/librarian/internal/serviceconfig"
2929 "github.com/googleapis/librarian/internal/sidekick/api"
3030 "github.com/googleapis/librarian/internal/sidekick/config"
31+ "github.com/googleapis/librarian/internal/sidekick/parser/httprule"
3132 "github.com/googleapis/librarian/internal/sidekick/parser/svcconfig"
3233 "github.com/googleapis/librarian/internal/sidekick/protobuf"
3334 "google.golang.org/genproto/googleapis/api/annotations"
@@ -48,7 +49,7 @@ func ParseProtobuf(cfg *config.Config) (*api.API, error) {
4849 if err != nil {
4950 return nil , err
5051 }
51- return makeAPIForProtobuf (serviceConfig , request ), nil
52+ return makeAPIForProtobuf (serviceConfig , request )
5253}
5354
5455func newCodeGeneratorRequest (source string , options map [string ]string ) (_ * pluginpb.CodeGeneratorRequest , err error ) {
@@ -180,7 +181,7 @@ const (
180181 enumDescriptorValue = 2
181182)
182183
183- func makeAPIForProtobuf (serviceConfig * serviceconfig.Service , req * pluginpb.CodeGeneratorRequest ) * api.API {
184+ func makeAPIForProtobuf (serviceConfig * serviceconfig.Service , req * pluginpb.CodeGeneratorRequest ) ( * api.API , error ) {
184185 var (
185186 mixinFileDesc []* descriptorpb.FileDescriptorProto
186187 enabledMixinMethods mixinMethods = make (map [string ]bool )
@@ -215,14 +216,18 @@ func makeAPIForProtobuf(serviceConfig *serviceconfig.Service, req *pluginpb.Code
215216 fFQN := "." + f .GetPackage ()
216217 for _ , m := range f .MessageType {
217218 mFQN := fFQN + "." + m .GetName ()
218- _ = processMessage (state , m , mFQN , f .GetPackage (), nil )
219+ if _ , err := processMessage (state , m , mFQN , f .GetPackage (), nil ); err != nil {
220+ return nil , err
221+ }
219222 }
220223
221224 for _ , e := range f .EnumType {
222225 eFQN := fFQN + "." + e .GetName ()
223226 _ = processEnum (state , e , eFQN , f .GetPackage (), nil )
224227 }
225- processResourceDefinitions (f , result )
228+ if err := processResourceDefinitions (f , result ); err != nil {
229+ return nil , err
230+ }
226231 }
227232
228233 // Then we need to add the messages, enums and services to the list of
@@ -257,9 +262,11 @@ func makeAPIForProtobuf(serviceConfig *serviceconfig.Service, req *pluginpb.Code
257262 service := processService (state , s , sFQN , f .GetPackage ())
258263 for _ , m := range s .Method {
259264 mFQN := sFQN + "." + m .GetName ()
260- if method := processMethod (state , m , mFQN , f .GetPackage (), sFQN ); method != nil {
261- service .Methods = append (service .Methods , method )
265+ method , err := processMethod (state , m , mFQN , f .GetPackage (), sFQN )
266+ if err != nil {
267+ return nil , err
262268 }
269+ service .Methods = append (service .Methods , method )
263270 }
264271 fileServices = append (fileServices , service )
265272 }
@@ -317,10 +324,12 @@ func makeAPIForProtobuf(serviceConfig *serviceconfig.Service, req *pluginpb.Code
317324 // define the mixin method in the code.
318325 continue
319326 }
320- if method := processMethod (state , m , mFQN , service .Package , sFQN ); method != nil {
321- applyServiceConfigMethodOverrides ( method , originalFQN , serviceConfig , result , mixin )
322- service . Methods = append ( service . Methods , method )
327+ method , err := processMethod (state , m , mFQN , service .Package , sFQN )
328+ if err != nil {
329+ return nil , err
323330 }
331+ applyServiceConfigMethodOverrides (method , originalFQN , serviceConfig , result , mixin )
332+ service .Methods = append (service .Methods , method )
324333 }
325334 }
326335 }
@@ -331,7 +340,7 @@ func makeAPIForProtobuf(serviceConfig *serviceconfig.Service, req *pluginpb.Code
331340 }
332341 updatePackageName (result )
333342 updateAutoPopulatedFields (serviceConfig , result )
334- return result
343+ return result , nil
335344}
336345
337346// requiresLongrunningMixin finds out if any method returns a LRO. This is used
@@ -437,16 +446,14 @@ func processService(state *api.APIState, s *descriptorpb.ServiceDescriptorProto,
437446 return service
438447}
439448
440- func processMethod (state * api.APIState , m * descriptorpb.MethodDescriptorProto , mFQN , packagez , serviceID string ) * api.Method {
449+ func processMethod (state * api.APIState , m * descriptorpb.MethodDescriptorProto , mFQN , packagez , serviceID string ) ( * api.Method , error ) {
441450 pathInfo , err := parsePathInfo (m , state )
442451 if err != nil {
443- slog .Error ("unsupported http method" , "method" , m , "error" , err )
444- return nil
452+ return nil , fmt .Errorf ("unsupported http method for %q: %w" , mFQN , err )
445453 }
446454 routing , err := parseRoutingAnnotations (mFQN , m )
447455 if err != nil {
448- slog .Error ("cannot parse routing annotations" , "method" , m , "err" , err )
449- return nil
456+ return nil , fmt .Errorf ("cannot parse routing annotations for %q: %w" , mFQN , err )
450457 }
451458 outputTypeID := m .GetOutputType ()
452459 method := & api.Method {
@@ -464,10 +471,10 @@ func processMethod(state *api.APIState, m *descriptorpb.MethodDescriptorProto, m
464471 SourceServiceID : serviceID ,
465472 }
466473 state .MethodByID [mFQN ] = method
467- return method
474+ return method , nil
468475}
469476
470- func processMessage (state * api.APIState , m * descriptorpb.DescriptorProto , mFQN , packagez string , parent * api.Message ) * api.Message {
477+ func processMessage (state * api.APIState , m * descriptorpb.DescriptorProto , mFQN , packagez string , parent * api.Message ) ( * api.Message , error ) {
471478 message := & api.Message {
472479 Name : m .GetName (),
473480 ID : mFQN ,
@@ -481,12 +488,17 @@ func processMessage(state *api.APIState, m *descriptorpb.DescriptorProto, mFQN,
481488 if opts .GetMapEntry () {
482489 message .IsMap = true
483490 }
484- processResourceAnnotation (opts , message )
491+ if err := processResourceAnnotation (opts , message ); err != nil {
492+ return nil , err
493+ }
485494 }
486495 if len (m .GetNestedType ()) > 0 {
487496 for _ , nm := range m .GetNestedType () {
488497 nmFQN := mFQN + "." + nm .GetName ()
489- nmsg := processMessage (state , nm , nmFQN , packagez , message )
498+ nmsg , err := processMessage (state , nm , nmFQN , packagez , message )
499+ if err != nil {
500+ return nil , err
501+ }
490502 message .Messages = append (message .Messages , nmsg )
491503 }
492504 }
@@ -514,7 +526,9 @@ func processMessage(state *api.APIState, m *descriptorpb.DescriptorProto, mFQN,
514526 AutoPopulated : protobufIsAutoPopulated (mf ),
515527 Behavior : protobufFieldBehavior (mf ),
516528 }
517- processResourceReference (mf , field )
529+ if err := processResourceReference (mf , field ); err != nil {
530+ return nil , err
531+ }
518532 normalizeTypes (state , mf , field )
519533 message .Fields = append (message .Fields , field )
520534 if field .IsOneOf {
@@ -536,46 +550,59 @@ func processMessage(state *api.APIState, m *descriptorpb.DescriptorProto, mFQN,
536550 message .OneOfs = message .OneOfs [:oneOfIdx ]
537551 }
538552
539- return message
553+ return message , nil
540554}
541555
542- func processResourceAnnotation (opts * descriptorpb.MessageOptions , message * api.Message ) {
556+ func processResourceAnnotation (opts * descriptorpb.MessageOptions , message * api.Message ) error {
543557 if ! proto .HasExtension (opts , annotations .E_Resource ) {
544- return
558+ return nil
545559 }
546560 ext := proto .GetExtension (opts , annotations .E_Resource )
547561 res , ok := ext .(* annotations.ResourceDescriptor )
548562 if ! ok {
549- return
563+ return fmt .Errorf ("in message %q: unexpected type for E_Resource extension: %T" , message .ID , ext )
564+ }
565+
566+ patterns , err := parseResourcePatterns (res .GetPattern ())
567+ if err != nil {
568+ return fmt .Errorf ("in message %q: %w" , message .ID , err )
550569 }
570+
551571 message .Resource = & api.Resource {
552572 Type : res .GetType (),
553- Pattern : res . GetPattern () ,
573+ Patterns : patterns ,
554574 Plural : res .GetPlural (),
555575 Singular : res .GetSingular (),
556576 Self : message ,
557577 }
578+ return nil
558579}
559580
560- func processResourceDefinitions (f * descriptorpb.FileDescriptorProto , result * api.API ) {
581+ func processResourceDefinitions (f * descriptorpb.FileDescriptorProto , result * api.API ) error {
561582 if f .Options == nil || ! proto .HasExtension (f .Options , annotations .E_ResourceDefinition ) {
562- return
583+ return nil
563584 }
564585
565586 ext := proto .GetExtension (f .Options , annotations .E_ResourceDefinition )
566587 res , ok := ext .([]* annotations.ResourceDescriptor )
567588 if ! ok {
568- return
589+ return fmt . Errorf ( "unexpected type for E_ResourceDefinition extension: %T" , ext )
569590 }
570591
571592 for _ , r := range res {
593+ patterns , err := parseResourcePatterns (r .GetPattern ())
594+ if err != nil {
595+ return fmt .Errorf ("in file %q: %w" , f .GetName (), err )
596+ }
597+
572598 result .ResourceDefinitions = append (result .ResourceDefinitions , & api.Resource {
573599 Type : r .GetType (),
574- Pattern : r . GetPattern () ,
600+ Patterns : patterns ,
575601 Plural : r .GetPlural (),
576602 Singular : r .GetSingular (),
577603 })
578604 }
605+ return nil
579606}
580607
581608// TODO(https://github.com/googleapis/librarian/issues/3036): This function needs
@@ -591,22 +618,23 @@ func processResourceDefinitions(f *descriptorpb.FileDescriptorProto, result *api
591618// distinction correctly. Future work should involve creating a more robust
592619// model that correctly determines the primary resource for a method, using
593620// `child_type` when it is present for collection-based methods.
594- func processResourceReference (f * descriptorpb.FieldDescriptorProto , field * api.Field ) {
621+ func processResourceReference (f * descriptorpb.FieldDescriptorProto , field * api.Field ) error {
595622 if f .Options == nil {
596- return
623+ return nil
597624 }
598625 if ! proto .HasExtension (f .Options , annotations .E_ResourceReference ) {
599- return
626+ return nil
600627 }
601628 ext := proto .GetExtension (f .Options , annotations .E_ResourceReference )
602629 ref , ok := ext .(* annotations.ResourceReference )
603630 if ! ok {
604- return
631+ return fmt . Errorf ( "in field %q: unexpected type for E_ResourceReference extension: %T" , field . ID , ext )
605632 }
606633 field .ResourceReference = & api.ResourceReference {
607634 Type : ref .Type ,
608635 ChildType : ref .ChildType ,
609636 }
637+ return nil
610638}
611639
612640func processEnum (state * api.APIState , e * descriptorpb.EnumDescriptorProto , eFQN , packagez string , parent * api.Message ) * api.Enum {
@@ -708,6 +736,18 @@ func addEnumDocumentation(state *api.APIState, p []int32, doc string, eFQN strin
708736 }
709737}
710738
739+ func parseResourcePatterns (patterns []string ) ([]api.ResourcePattern , error ) {
740+ var parsedPatterns []api.ResourcePattern
741+ for _ , p := range patterns {
742+ tmpl , err := httprule .ParseResourcePattern (p )
743+ if err != nil {
744+ return nil , fmt .Errorf ("failed to parse resource pattern %q: %w" , p , err )
745+ }
746+ parsedPatterns = append (parsedPatterns , tmpl .Segments )
747+ }
748+ return parsedPatterns , nil
749+ }
750+
711751// trimLeadingSpacesInDocumentation removes the leading spaces from each line in the documentation.
712752// Protobuf removes the `//` leading characters, but leaves the leading
713753// whitespace. It is easier to reason about the comments in the rest of the
0 commit comments