diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 79b4c9fc..a161edea 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,7 +19,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v3 with: - go-version: 1.23 + go-version: 1.25.0 id: go - name: Checkout code @@ -38,7 +38,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v3 with: - go-version: 1.23 + go-version: 1.25.0 id: go - name: Checkout code diff --git a/datamodel/high/base/schema.go b/datamodel/high/base/schema.go index 9a1793e0..f606676f 100644 --- a/datamodel/high/base/schema.go +++ b/datamodel/high/base/schema.go @@ -568,6 +568,12 @@ func (s *Schema) RenderInline() ([]byte, error) { // MarshalYAML will create a ready to render YAML representation of the Schema object. func (s *Schema) MarshalYAML() (interface{}, error) { + if s.ParentProxy != nil { + if node, ok, err := s.ParentProxy.renderTransformedRefWithSiblings(s); ok || err != nil { + return node, err + } + } + nb := high.NewNodeBuilder(s, s.low) // determine index version diff --git a/datamodel/high/base/schema_proxy.go b/datamodel/high/base/schema_proxy.go index 84b4ce2e..d9bae1ef 100644 --- a/datamodel/high/base/schema_proxy.go +++ b/datamodel/high/base/schema_proxy.go @@ -412,6 +412,134 @@ func (sp *SchemaProxy) renderRefWithSiblings() *yaml.Node { return node } +func (sp *SchemaProxy) renderTransformedRefWithSiblings(s *Schema) (*yaml.Node, bool, error) { + if sp == nil || sp.schema == nil || sp.schema.Value == nil || sp.schema.Value.TransformedRef == nil || s == nil { + return nil, false, nil + } + if !sp.shouldCollapseTransformedRefWithSiblings() { + return nil, false, nil + } + if len(s.AllOf) != 2 || s.AllOf[0] == nil || s.AllOf[1] == nil || !s.AllOf[1].IsReference() { + return nil, false, nil + } + + // Only collapse the synthetic allOf created by the sibling-ref transformer. + // If callers add fields to the outer schema or change its composition, keep + // the explicit allOf so no mutations are hidden. + outerNode := high.NewNodeBuilder(s, s.low).Render() + if len(outerNode.Content) != 2 || outerNode.Content[0].Value != "allOf" { + return nil, false, nil + } + + siblingRender, err := s.AllOf[0].MarshalYAML() + if err != nil { + return nil, true, err + } + siblingNode, ok := yamlNodeFromRender(siblingRender) + if !ok || !utils.IsNodeMap(siblingNode) { + return nil, false, nil + } + + ref := s.AllOf[1].GetReference() + original := sp.schema.Value.TransformedRef + result := utils.CreateEmptyMapNode() + consumed := make(map[string]struct{}, len(siblingNode.Content)/2) + + for i := 0; i+1 < len(original.Content); i += 2 { + keyNode := original.Content[i] + valueNode := original.Content[i+1] + if keyNode == nil { + continue + } + if keyNode.Value == "$ref" { + refKey := cloneYAMLNode(keyNode) + refValue := cloneYAMLNode(valueNode) + if refValue == nil { + refValue = utils.CreateStringNode(ref) + } + refValue.Value = ref + result.Content = append(result.Content, refKey, refValue) + continue + } + if _, siblingValue := findYAMLPair(siblingNode, keyNode.Value); siblingValue != nil { + renderKey := cloneYAMLNode(keyNode) + result.Content = append(result.Content, renderKey, cloneYAMLNode(siblingValue)) + consumed[keyNode.Value] = struct{}{} + } + } + + for i := 0; i+1 < len(siblingNode.Content); i += 2 { + keyNode := siblingNode.Content[i] + valueNode := siblingNode.Content[i+1] + if _, ok := consumed[keyNode.Value]; ok { + continue + } + result.Content = append(result.Content, cloneYAMLNode(keyNode), cloneYAMLNode(valueNode)) + } + + return result, true, nil +} + +func (sp *SchemaProxy) shouldCollapseTransformedRefWithSiblings() bool { + if sp == nil || sp.schema == nil || sp.schema.Value == nil { + return false + } + idx := sp.schema.Value.GetIndex() + if idx == nil || idx.GetConfig() == nil || idx.GetConfig().SpecInfo == nil { + return true + } + return idx.GetConfig().SpecInfo.VersionNumeric >= 3.1 +} + +func yamlNodeFromRender(rendered interface{}) (*yaml.Node, bool) { + switch node := rendered.(type) { + case *yaml.Node: + return node, node != nil + case yaml.Node: + return &node, true + default: + return nil, false + } +} + +func findYAMLPair(node *yaml.Node, key string) (*yaml.Node, *yaml.Node) { + if node == nil || !utils.IsNodeMap(node) { + return nil, nil + } + for i := 0; i+1 < len(node.Content); i += 2 { + if node.Content[i] != nil && node.Content[i].Value == key { + return node.Content[i], node.Content[i+1] + } + } + return nil, nil +} + +func cloneYAMLNode(node *yaml.Node) *yaml.Node { + if node == nil { + return nil + } + clone := &yaml.Node{ + Kind: node.Kind, + Style: node.Style, + Tag: node.Tag, + Value: node.Value, + Anchor: node.Anchor, + Alias: node.Alias, + Line: node.Line, + Column: node.Column, + HeadComment: node.HeadComment, + LineComment: node.LineComment, + FootComment: node.FootComment, + } + if len(node.Content) > 0 { + clone.Content = make([]*yaml.Node, len(node.Content)) + for i, child := range node.Content { + clone.Content[i] = cloneYAMLNode(child) + } + } + return clone +} + // Render will return a YAML representation of the Schema object as a byte slice. func (sp *SchemaProxy) Render() ([]byte, error) { return yaml.Marshal(sp) @@ -424,6 +552,9 @@ func (sp *SchemaProxy) MarshalYAML() (interface{}, error) { if err != nil { return nil, err } + if node, ok, renderErr := sp.renderTransformedRefWithSiblings(s); ok || renderErr != nil { + return node, renderErr + } nb := high.NewNodeBuilder(s, s.low) return nb.Render(), nil } diff --git a/datamodel/high/base/schema_proxy_test.go b/datamodel/high/base/schema_proxy_test.go index c00d8859..79e80b49 100644 --- a/datamodel/high/base/schema_proxy_test.go +++ b/datamodel/high/base/schema_proxy_test.go @@ -13,6 +13,7 @@ import ( "sync" "testing" + "github.com/pb33f/libopenapi/datamodel" "github.com/pb33f/libopenapi/datamodel/low" lowbase "github.com/pb33f/libopenapi/datamodel/low/base" "github.com/pb33f/libopenapi/index" @@ -1697,6 +1698,253 @@ func TestCreateSchemaProxyRefWithSchema_BundlingMode(t *testing.T) { assert.Contains(t, string(rendered), "description: bundled sibling") } +func TestSchemaProxy_RenderTransformedRefWithSiblings(t *testing.T) { + deprecated := true + lowProxy := &lowbase.SchemaProxy{ + TransformedRef: &yaml.Node{ + Kind: yaml.MappingNode, + Tag: "!!map", + Content: []*yaml.Node{ + nil, + utils.CreateStringNode("ignored"), + utils.CreateStringNode("$ref"), + nil, + utils.CreateStringNode("description"), + utils.CreateStringNode("original description"), + }, + }, + } + sp := NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{Value: lowProxy}) + schema := &Schema{ + AllOf: []*SchemaProxy{ + CreateSchemaProxy(&Schema{ + Description: "updated description", + Title: "added title", + Deprecated: &deprecated, + }), + CreateSchemaProxyRef("#/components/schemas/Thing"), + }, + } + + node, ok, err := sp.renderTransformedRefWithSiblings(schema) + require.NoError(t, err) + require.True(t, ok) + require.NotNil(t, node) + require.Len(t, node.Content, 8) + assert.Equal(t, "$ref", node.Content[0].Value) + assert.Equal(t, "#/components/schemas/Thing", node.Content[1].Value) + assert.Equal(t, "description", node.Content[2].Value) + assert.Equal(t, "updated description", node.Content[3].Value) + assert.Equal(t, "title", node.Content[4].Value) + assert.Equal(t, "added title", node.Content[5].Value) + assert.Equal(t, "deprecated", node.Content[6].Value) + assert.Equal(t, "true", node.Content[7].Value) +} + +func TestSchemaProxy_MarshalYAML_TransformedRefWithSiblings(t *testing.T) { + lowProxy := &lowbase.SchemaProxy{ + TransformedRef: &yaml.Node{ + Kind: yaml.MappingNode, + Tag: "!!map", + Content: []*yaml.Node{ + utils.CreateStringNode("$ref"), + utils.CreateStringNode("#/components/schemas/Thing"), + utils.CreateStringNode("description"), + utils.CreateStringNode("original description"), + }, + }, + } + sp := NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{Value: lowProxy}) + schema := &Schema{ + ParentProxy: sp, + AllOf: []*SchemaProxy{ + CreateSchemaProxy(&Schema{Description: "updated description"}), + CreateSchemaProxyRef("#/components/schemas/Thing"), + }, + } + sp.rendered = schema + + rendered, err := sp.MarshalYAML() + require.NoError(t, err) + node, ok := rendered.(*yaml.Node) + require.True(t, ok) + assert.Equal(t, "$ref", node.Content[0].Value) + assert.Equal(t, "#/components/schemas/Thing", node.Content[1].Value) + assert.Equal(t, "description", node.Content[2].Value) + assert.Equal(t, "updated description", node.Content[3].Value) + + rendered, err = schema.MarshalYAML() + require.NoError(t, err) + node, ok = rendered.(*yaml.Node) + require.True(t, ok) + assert.Equal(t, "$ref", node.Content[0].Value) + assert.Equal(t, "#/components/schemas/Thing", node.Content[1].Value) + assert.Equal(t, "description", node.Content[2].Value) + assert.Equal(t, "updated description", node.Content[3].Value) +} + +func TestSchemaProxy_RenderTransformedRefWithSiblingsFallbacks(t *testing.T) { + deprecated := true + lowProxy := &lowbase.SchemaProxy{ + TransformedRef: &yaml.Node{ + Kind: yaml.MappingNode, + Tag: "!!map", + Content: []*yaml.Node{ + utils.CreateStringNode("$ref"), + utils.CreateStringNode("#/components/schemas/Thing"), + utils.CreateStringNode("deprecated"), + utils.CreateBoolNode("true"), + }, + }, + } + sp := NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{Value: lowProxy}) + + node, ok, err := (*SchemaProxy)(nil).renderTransformedRefWithSiblings(&Schema{}) + require.NoError(t, err) + assert.False(t, ok) + assert.Nil(t, node) + + node, ok, err = sp.renderTransformedRefWithSiblings(&Schema{}) + require.NoError(t, err) + assert.False(t, ok) + assert.Nil(t, node) + + node, ok, err = sp.renderTransformedRefWithSiblings(&Schema{ + Description: "outer mutation", + AllOf: []*SchemaProxy{ + CreateSchemaProxy(&Schema{Deprecated: &deprecated}), + CreateSchemaProxyRef("#/components/schemas/Thing"), + }, + }) + require.NoError(t, err) + assert.False(t, ok) + assert.Nil(t, node) + + badSibling := &SchemaProxy{buildError: errors.New("boom")} + node, ok, err = sp.renderTransformedRefWithSiblings(&Schema{ + AllOf: []*SchemaProxy{ + badSibling, + CreateSchemaProxyRef("#/components/schemas/Thing"), + }, + }) + require.Error(t, err) + assert.True(t, ok) + assert.Nil(t, node) + + scalarRefLowProxy := &lowbase.SchemaProxy{} + scalarRefLowProxy.SetReference("#/components/schemas/Scalar", utils.CreateStringNode("#/components/schemas/Scalar")) + node, ok, err = sp.renderTransformedRefWithSiblings(&Schema{ + AllOf: []*SchemaProxy{ + NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{Value: scalarRefLowProxy}), + CreateSchemaProxyRef("#/components/schemas/Thing"), + }, + }) + require.NoError(t, err) + assert.False(t, ok) + assert.Nil(t, node) +} + +func TestSchemaProxy_RenderTransformedRefWithSiblings_OpenAPI30Fallback(t *testing.T) { + var root yaml.Node + require.NoError(t, yaml.Unmarshal([]byte("$ref: '#/components/schemas/Thing'\nminLength: 2"), &root)) + + cfg := index.CreateOpenAPIIndexConfig() + cfg.SpecInfo = &datamodel.SpecInfo{VersionNumeric: 3.0} + cfg.TransformSiblingRefs = true + idx := index.NewSpecIndexWithConfig(&root, cfg) + + lowProxy := new(lowbase.SchemaProxy) + require.NoError(t, lowProxy.Build(context.Background(), nil, root.Content[0], idx)) + require.NotNil(t, lowProxy.TransformedRef) + + minLength := int64(2) + sp := NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{Value: lowProxy}) + schema := &Schema{ + AllOf: []*SchemaProxy{ + CreateSchemaProxy(&Schema{MinLength: &minLength}), + CreateSchemaProxyRef("#/components/schemas/Thing"), + }, + } + + node, ok, err := sp.renderTransformedRefWithSiblings(schema) + require.NoError(t, err) + assert.False(t, ok) + assert.Nil(t, node) + assert.False(t, sp.shouldCollapseTransformedRefWithSiblings()) +} + +func TestSchemaProxy_ShouldCollapseTransformedRefWithSiblings(t *testing.T) { + require.False(t, (*SchemaProxy)(nil).shouldCollapseTransformedRefWithSiblings()) + + lowProxy := &lowbase.SchemaProxy{} + noIndex := NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{Value: lowProxy}) + require.True(t, noIndex.shouldCollapseTransformedRefWithSiblings()) + + var root yaml.Node + require.NoError(t, yaml.Unmarshal([]byte("type: string"), &root)) + + cfg30 := index.CreateOpenAPIIndexConfig() + cfg30.SpecInfo = &datamodel.SpecInfo{VersionNumeric: 3.0} + idx30 := index.NewSpecIndexWithConfig(&root, cfg30) + low30 := new(lowbase.SchemaProxy) + require.NoError(t, low30.Build(context.Background(), nil, root.Content[0], idx30)) + assert.False(t, NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{Value: low30}).shouldCollapseTransformedRefWithSiblings()) + + cfg31 := index.CreateOpenAPIIndexConfig() + cfg31.SpecInfo = &datamodel.SpecInfo{VersionNumeric: 3.1} + idx31 := index.NewSpecIndexWithConfig(&root, cfg31) + low31 := new(lowbase.SchemaProxy) + require.NoError(t, low31.Build(context.Background(), nil, root.Content[0], idx31)) + assert.True(t, NewSchemaProxy(&low.NodeReference[*lowbase.SchemaProxy]{Value: low31}).shouldCollapseTransformedRefWithSiblings()) +} + +func TestTransformedRefRenderHelpers(t *testing.T) { + node := utils.CreateStringNode("value") + ptrNode, ok := yamlNodeFromRender(node) + require.True(t, ok) + assert.Equal(t, node, ptrNode) + + valueNode, ok := yamlNodeFromRender(*node) + require.True(t, ok) + assert.Equal(t, "value", valueNode.Value) + + noNode, ok := yamlNodeFromRender("nope") + assert.False(t, ok) + assert.Nil(t, noNode) + + key, value := findYAMLPair(nil, "description") + assert.Nil(t, key) + assert.Nil(t, value) + + key, value = findYAMLPair(utils.CreateStringNode("scalar"), "description") + assert.Nil(t, key) + assert.Nil(t, value) + + mapping := &yaml.Node{ + Kind: yaml.MappingNode, + Tag: "!!map", + Content: []*yaml.Node{ + utils.CreateStringNode("description"), + utils.CreateStringNode("hello"), + }, + } + key, value = findYAMLPair(mapping, "description") + require.NotNil(t, key) + require.NotNil(t, value) + assert.Equal(t, "hello", value.Value) + + key, value = findYAMLPair(mapping, "title") + assert.Nil(t, key) + assert.Nil(t, value) + + assert.Nil(t, cloneYAMLNode(nil)) + cloned := cloneYAMLNode(mapping) + require.NotNil(t, cloned) + assert.Equal(t, mapping.Content[0].Value, cloned.Content[0].Value) + cloned.Content[0].Value = "changed" + assert.Equal(t, "description", mapping.Content[0].Value) +} + func TestCreateSchemaProxyRefWithSchema_IsRefWithSiblings(t *testing.T) { // Verify isRefWithSiblings() returns correct values for all factory types refOnly := CreateSchemaProxyRef("#/components/schemas/Pet") diff --git a/document_test.go b/document_test.go index 33a71cd1..d6815255 100644 --- a/document_test.go +++ b/document_test.go @@ -1927,10 +1927,12 @@ components: renderedStr := string(renderedBytes) - // verify the rendered spec contains the transformed structure - assert.Contains(t, renderedStr, "allOf:") - assert.Contains(t, renderedStr, "- title: destination-amazon-sqs") - assert.Contains(t, renderedStr, "- $ref: '#/components/schemas/destination-base'") + // Default rendering preserves the authored $ref+sibling syntax while the + // model still carries the transformed allOf semantics. + assert.Equal(t, spec+"\n", renderedStr) + assert.NotContains(t, renderedStr, "allOf:") + assert.Contains(t, renderedStr, "title: destination-amazon-sqs") + assert.Contains(t, renderedStr, "$ref: '#/components/schemas/destination-base'") // verify the reloaded model has the correct structure if reloadedV3Doc.Model.Components != nil && reloadedV3Doc.Model.Components.Schemas != nil { @@ -1990,10 +1992,12 @@ components: renderedStr := string(renderedBytes) - // verify the rendered spec contains the transformed structure - assert.Contains(t, renderedStr, "allOf:") - assert.Contains(t, renderedStr, "- description: destination-amazon-sqs") - assert.Contains(t, renderedStr, "- $ref: '#/components/schemas/destination-base'") + // Default rendering preserves the authored $ref+sibling syntax and order + // while the model still carries the transformed allOf semantics. + assert.Equal(t, spec+"\n", renderedStr) + assert.NotContains(t, renderedStr, "allOf:") + assert.Contains(t, renderedStr, "$ref: '#/components/schemas/destination-base'") + assert.Contains(t, renderedStr, "description: destination-amazon-sqs") // verify the reloaded model has the correct structure if reloadedV3Doc.Model.Components != nil && reloadedV3Doc.Model.Components.Schemas != nil { @@ -2265,6 +2269,148 @@ components: assertSiblingRefAllOf(t, mediaType.Schema) } +func TestDocument_Render_Issue575_PreservesSiblingRefSyntax(t *testing.T) { + bs, err := os.ReadFile("test_specs/issue-575-sibling-ref-render.yaml") + require.NoError(t, err) + + doc, err := NewDocument(bs) + require.NoError(t, err) + + model, err := doc.BuildV3Model() + require.NoError(t, err) + + shipper := model.Model.Components.Schemas.GetOrZero("cmd.protoc_gen_go.testdata.proto.Shipper").Schema() + require.NotNil(t, shipper) + + createTime := shipper.Properties.GetOrZero("createTime") + require.NotNil(t, createTime) + require.NotNil(t, createTime.GoLow()) + assert.NotNil(t, createTime.GoLow().TransformedRef) + + createTimeSchema := createTime.Schema() + require.NotNil(t, createTimeSchema) + require.Len(t, createTimeSchema.AllOf, 2) + assert.Equal(t, "The creation timestamp of the shipper.", createTimeSchema.AllOf[0].Schema().Description) + assert.Equal(t, "#/components/schemas/Timestamp", createTimeSchema.AllOf[1].GetReference()) + + schemaBytes, err := createTimeSchema.Render() + require.NoError(t, err) + assert.Equal(t, "$ref: '#/components/schemas/Timestamp'\ndescription: The creation timestamp of the shipper.\n", string(schemaBytes)) + + modelBytes, err := model.Model.Render() + require.NoError(t, err) + assert.Equal(t, strings.ReplaceAll(string(bs), "\r\n", "\n"), string(modelBytes)) +} + +func TestDocument_Render_Issue575_UsesMutatedSiblingValues(t *testing.T) { + bs, err := os.ReadFile("test_specs/issue-575-sibling-ref-render.yaml") + require.NoError(t, err) + + doc, err := NewDocument(bs) + require.NoError(t, err) + + model, err := doc.BuildV3Model() + require.NoError(t, err) + + shipper := model.Model.Components.Schemas.GetOrZero("cmd.protoc_gen_go.testdata.proto.Shipper").Schema() + require.NotNil(t, shipper) + + createTime := shipper.Properties.GetOrZero("createTime") + require.NotNil(t, createTime) + + createTimeSchema := createTime.Schema() + require.NotNil(t, createTimeSchema) + require.Len(t, createTimeSchema.AllOf, 2) + + siblingSchema := createTimeSchema.AllOf[0].Schema() + require.NotNil(t, siblingSchema) + siblingSchema.Description = "The created timestamp from libopenapi." + + modelBytes, err := model.Model.Render() + require.NoError(t, err) + + expected := strings.Replace( + strings.ReplaceAll(string(bs), "\r\n", "\n"), + "The creation timestamp of the shipper.", + "The created timestamp from libopenapi.", + 1, + ) + assert.Equal(t, expected, string(modelBytes)) +} + +func TestDocument_Render_AuthoredAllOfWithRefSiblingShapeStaysAllOf(t *testing.T) { + spec := `openapi: 3.1.0 +info: + title: Authored allOf + version: 1.0.0 +paths: {} +components: + schemas: + Name: + type: string + Composed: + allOf: + - description: keep authored allOf + - $ref: '#/components/schemas/Name' +` + + doc, err := NewDocument([]byte(spec)) + require.NoError(t, err) + + model, err := doc.BuildV3Model() + require.NoError(t, err) + + composed := model.Model.Components.Schemas.GetOrZero("Composed") + require.NotNil(t, composed) + require.NotNil(t, composed.GoLow()) + assert.Nil(t, composed.GoLow().TransformedRef) + + modelBytes, err := model.Model.Render() + require.NoError(t, err) + assert.Contains(t, string(modelBytes), "allOf:") + assert.Contains(t, string(modelBytes), "- $ref: '#/components/schemas/Name'") +} + +func TestDocument_Render_OpenAPI30TransformedSiblingRefStaysAllOf(t *testing.T) { + spec := `openapi: 3.0.3 +info: + title: OAS 3.0 sibling refs + version: 1.0.0 +paths: {} +components: + schemas: + Name: + type: string + ConstrainedName: + $ref: '#/components/schemas/Name' + minLength: 2 +` + + doc, err := NewDocument([]byte(spec)) + require.NoError(t, err) + + model, err := doc.BuildV3Model() + require.NoError(t, err) + + constrained := model.Model.Components.Schemas.GetOrZero("ConstrainedName") + require.NotNil(t, constrained) + require.NotNil(t, constrained.GoLow()) + assert.NotNil(t, constrained.GoLow().TransformedRef) + + constrainedSchema := constrained.Schema() + require.NotNil(t, constrainedSchema) + require.Len(t, constrainedSchema.AllOf, 2) + require.NotNil(t, constrainedSchema.AllOf[0].Schema().MinLength) + assert.Equal(t, int64(2), *constrainedSchema.AllOf[0].Schema().MinLength) + + modelBytes, err := model.Model.Render() + require.NoError(t, err) + rendered := string(modelBytes) + assert.Contains(t, rendered, "ConstrainedName:\n allOf:") + assert.Contains(t, rendered, "- minLength: 2") + assert.Contains(t, rendered, "- $ref: '#/components/schemas/Name'") +} + func assertSiblingRefAllOf(t *testing.T, proxy *base.SchemaProxy) { t.Helper() diff --git a/test_specs/issue-575-sibling-ref-render.yaml b/test_specs/issue-575-sibling-ref-render.yaml new file mode 100644 index 00000000..419ede77 --- /dev/null +++ b/test_specs/issue-575-sibling-ref-render.yaml @@ -0,0 +1,56 @@ +openapi: 3.1.2 +paths: {} +components: + schemas: + Timestamp: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + type: string + title: Timestamp + format: date-time + cmd.protoc_gen_go.testdata.proto.GetShipperRequest: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + type: object + properties: + name: + type: string + description: |- + The resource name of the shipper to retrieve. + Format: shippers/{shipper} + title: Get Shipper Request + required: + - name + additionalProperties: true + description: Request message for FreightService.GetShipper. + cmd.protoc_gen_go.testdata.proto.Shipper: + $schema: https://spec.openapis.org/oas/3.1/dialect/base + type: object + properties: + name: + type: string + description: The resource name of the shipper. + createTime: + $ref: '#/components/schemas/Timestamp' + description: The creation timestamp of the shipper. + updateTime: + $ref: '#/components/schemas/Timestamp' + title: The last update timestamp of the shipper. + description: Updated when create/update/delete operation is performed. + deleteTime: + $ref: '#/components/schemas/Timestamp' + description: The deletion timestamp of the shipper. + displayName: + type: string + description: The display name of the shipper. + billingAccount: + type: string + description: The billing account of the shipper. + etag: + type: string + description: The current etag value of the shipper. + title: Shipper + required: + - billingAccount + - displayName + - name + additionalProperties: true + description: A shipper is a supplier or owner of goods to be transported.