github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/market/service_proposal.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package market
    19  
    20  import (
    21  	"encoding/json"
    22  
    23  	validation "github.com/go-ozzo/ozzo-validation"
    24  	"github.com/mysteriumnetwork/node/p2p/compat"
    25  	"github.com/mysteriumnetwork/node/utils/validateutil"
    26  )
    27  
    28  const (
    29  	proposalFormat = "service-proposal/v3"
    30  )
    31  
    32  // ServiceProposal is top level structure which is presented to marketplace by service provider, and looked up by service consumer
    33  // service proposal can be marked as unsupported by deserializer, because of unknown service, payment method, or contact type
    34  type ServiceProposal struct {
    35  	ID int64 `json:"id"`
    36  
    37  	// A version number is included in the proposal to allow extensions to the proposal format
    38  	Format string `json:"format"`
    39  
    40  	Compatibility int `json:"compatibility"`
    41  
    42  	// Unique identifier of a provider
    43  	ProviderID string `json:"provider_id"`
    44  
    45  	// Type of service type offered
    46  	ServiceType string `json:"service_type"`
    47  
    48  	// Service location
    49  	Location Location `json:"location"`
    50  
    51  	// Communication methods possible
    52  	Contacts ContactList `json:"contacts"`
    53  
    54  	// AccessPolicies represents the access controls for proposal
    55  	AccessPolicies *[]AccessPolicy `json:"access_policies,omitempty"`
    56  
    57  	// Quality represents the service quality.
    58  	Quality Quality `json:"quality"`
    59  }
    60  
    61  // NewProposalOpts optional params for the new proposal creation.
    62  type NewProposalOpts struct {
    63  	Location       *Location
    64  	AccessPolicies []AccessPolicy
    65  	Contacts       []Contact
    66  	Quality        *Quality
    67  }
    68  
    69  // NewProposal creates a new proposal.
    70  func NewProposal(providerID, serviceType string, opts NewProposalOpts) ServiceProposal {
    71  	p := ServiceProposal{
    72  		Format:         proposalFormat,
    73  		Compatibility:  compat.Compatibility,
    74  		ProviderID:     providerID,
    75  		ServiceType:    serviceType,
    76  		Location:       Location{},
    77  		Contacts:       nil,
    78  		AccessPolicies: nil,
    79  	}
    80  	if loc := opts.Location; loc != nil {
    81  		p.Location = *loc
    82  	}
    83  	if ap := opts.AccessPolicies; ap != nil {
    84  		p.AccessPolicies = &ap
    85  	}
    86  	if c := opts.Contacts; c != nil {
    87  		p.Contacts = c
    88  	}
    89  	if q := opts.Quality; q != nil {
    90  		p.Quality = *q
    91  	}
    92  	return p
    93  }
    94  
    95  // Validate validates the proposal.
    96  func (proposal *ServiceProposal) Validate() error {
    97  	return validation.ValidateStruct(proposal,
    98  		validation.Field(&proposal.Format, validation.Required, validation.By(validateutil.StringEquals(proposalFormat))),
    99  		validation.Field(&proposal.ProviderID, validation.Required),
   100  		validation.Field(&proposal.ServiceType, validation.Required),
   101  		validation.Field(&proposal.Location, validation.Required),
   102  		validation.Field(&proposal.Contacts, validation.Required),
   103  	)
   104  }
   105  
   106  // UniqueID returns unique proposal composite ID
   107  func (proposal *ServiceProposal) UniqueID() ProposalID {
   108  	return ProposalID{
   109  		ProviderID:  proposal.ProviderID,
   110  		ServiceType: proposal.ServiceType,
   111  	}
   112  }
   113  
   114  // UnmarshalJSON is custom json unmarshaler to dynamically fill in ServiceProposal values
   115  func (proposal *ServiceProposal) UnmarshalJSON(data []byte) error {
   116  	var jsonData struct {
   117  		ID             int64            `json:"id"`
   118  		Format         string           `json:"format"`
   119  		ProviderID     string           `json:"provider_id"`
   120  		ServiceType    string           `json:"service_type"`
   121  		Compatibility  int              `json:"compatibility"`
   122  		Location       Location         `json:"location"`
   123  		Contacts       *json.RawMessage `json:"contacts"`
   124  		AccessPolicies *[]AccessPolicy  `json:"access_policies,omitempty"`
   125  		Quality        Quality          `json:"quality"`
   126  	}
   127  	if err := json.Unmarshal(data, &jsonData); err != nil {
   128  		return err
   129  	}
   130  
   131  	proposal.ID = jsonData.ID
   132  	proposal.Format = jsonData.Format
   133  	proposal.ProviderID = jsonData.ProviderID
   134  	proposal.ServiceType = jsonData.ServiceType
   135  	proposal.Compatibility = jsonData.Compatibility
   136  	proposal.Location = jsonData.Location
   137  
   138  	// run contact unserializer
   139  	proposal.Contacts = unserializeContacts(jsonData.Contacts)
   140  	proposal.AccessPolicies = jsonData.AccessPolicies
   141  	proposal.Quality = jsonData.Quality
   142  
   143  	return nil
   144  }
   145  
   146  // IsSupported returns true if this service proposal can be used for connections by service consumer
   147  // can be used as a filter to filter out all proposals which are unsupported for any reason
   148  func (proposal *ServiceProposal) IsSupported() bool {
   149  	if _, ok := supportedServices[proposal.ServiceType]; !ok {
   150  		return false
   151  	}
   152  
   153  	for _, contact := range proposal.Contacts {
   154  		if _, notSupported := contact.Definition.(UnsupportedContactType); notSupported {
   155  			continue
   156  		}
   157  		//at least one is supported - we are ok
   158  		return true
   159  	}
   160  
   161  	return false
   162  }
   163  
   164  var supportedServices = make(map[string]struct{})
   165  
   166  // RegisterServiceType registers a supported service type.
   167  func RegisterServiceType(serviceType string) {
   168  	supportedServices[serviceType] = struct{}{}
   169  }