/vendor/github.com/manicminer/hamilton/odata/odata.go

https://github.com/terraform-providers/terraform-provider-azuread · Go · 184 lines · 149 code · 26 blank · 9 comment · 42 complexity · 618a9f7e85dbc4776e66d9d2fc3362fa MD5 · raw file

  1. package odata
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/url"
  6. "regexp"
  7. "strings"
  8. "github.com/hashicorp/go-uuid"
  9. )
  10. const ODataVersion = "4.0" // TODO: support 4.01 - https://docs.oasis-open.org/odata/odata-json-format/v4.01/cs01/odata-json-format-v4.01-cs01.html#_Toc499720587
  11. type Id string
  12. func (o Id) MarshalJSON() ([]byte, error) {
  13. id := regexp.MustCompile(`/v2/`).ReplaceAllString(string(o), `/v1.0/`)
  14. u, err := url.Parse(id)
  15. if err != nil || u.Scheme == "" || u.Host == "" {
  16. matches := regexp.MustCompile(`([a-zA-Z]+)\(['"]([^'"]+)['"]\)`).FindStringSubmatch(id)
  17. if len(matches) != 3 {
  18. return nil, fmt.Errorf("Marshaling odata.Id: could not match a GUID")
  19. }
  20. objectType := matches[1]
  21. guid := matches[2]
  22. if _, err = uuid.ParseUUID(guid); err != nil {
  23. return nil, fmt.Errorf("Marshaling odata.Id: %+v", err)
  24. }
  25. // Although we're hard-coding `graph.microsoft.com` here, this doesn't _appear_ to be a problem
  26. // The host can seemingly be anything, even complete nonsense, and the API will accept it provided
  27. // it can parse out a version number, an object type and a GUID.
  28. id = fmt.Sprintf("https://graph.microsoft.com/v1.0/%s/%s", objectType, guid)
  29. }
  30. return json.Marshal(id)
  31. }
  32. func (o *Id) UnmarshalJSON(data []byte) error {
  33. var id string
  34. if err := json.Unmarshal(data, &id); err != nil {
  35. return err
  36. }
  37. *o = Id(regexp.MustCompile(`/v2/`).ReplaceAllString(id, `/v1.0/`))
  38. return nil
  39. }
  40. type Link string
  41. func (o *Link) UnmarshalJSON(data []byte) error {
  42. var link string
  43. if err := json.Unmarshal(data, &link); err != nil {
  44. return err
  45. }
  46. *o = Link(regexp.MustCompile(`/v2/`).ReplaceAllString(link, `/v1.0/`))
  47. return nil
  48. }
  49. // OData is used to unmarshall OData metadata from an API response.
  50. type OData struct {
  51. Context *string `json:"@odata.context"`
  52. MetadataEtag *string `json:"@odata.metadataEtag"`
  53. Type *Type `json:"@odata.type"`
  54. Count *int `json:"@odata.count"`
  55. NextLink *string `json:"@odata.nextLink"`
  56. Delta *string `json:"@odata.delta"`
  57. DeltaLink *string `json:"@odata.deltaLink"`
  58. Id *Id `json:"@odata.id"`
  59. EditLink *Link `json:"@odata.editLink"`
  60. Etag *string `json:"@odata.etag"`
  61. Error *Error `json:"-"`
  62. Value interface{} `json:"value"`
  63. }
  64. func (o *OData) UnmarshalJSON(data []byte) error {
  65. // Perform unmarshalling using a local type
  66. type odata OData
  67. var o2 odata
  68. if err := json.Unmarshal(data, &o2); err != nil {
  69. return err
  70. }
  71. *o = OData(o2)
  72. // Look for errors in the "error" and "odata.error" fields
  73. var e map[string]json.RawMessage
  74. if err := json.Unmarshal(data, &e); err != nil {
  75. return err
  76. }
  77. for _, k := range []string{"error", "odata.error"} {
  78. if v, ok := e[k]; ok {
  79. var e2 Error
  80. if err := json.Unmarshal(v, &e2); err != nil {
  81. return err
  82. }
  83. o.Error = &e2
  84. break
  85. }
  86. }
  87. return nil
  88. }
  89. // Error is used to unmarshal an API error message.
  90. type Error struct {
  91. Code *string `json:"code"`
  92. Date *string `json:"date"`
  93. Message *string `json:"-"`
  94. RawMessage *json.RawMessage `json:"message"` // sometimes a string, sometimes an object :/
  95. ClientRequestId *string `json:"client-request-id"`
  96. RequestId *string `json:"request-id"`
  97. InnerError *Error `json:"innerError"` // nested errors
  98. Details *[]struct {
  99. Code *string `json:"code"`
  100. Target *string `json:"target"`
  101. } `json:"details"`
  102. Values *[]struct {
  103. Item string `json:"item"`
  104. Value string `json:"value"`
  105. } `json:"values"`
  106. }
  107. func (e *Error) UnmarshalJSON(data []byte) error {
  108. // Perform unmarshalling using a local type
  109. type error Error
  110. var e2 error
  111. if err := json.Unmarshal(data, &e2); err != nil {
  112. return err
  113. }
  114. *e = Error(e2)
  115. // Unmarshal the message, which can be a plain string or an object wrapping a message
  116. if raw := e.RawMessage; raw != nil && len(*raw) > 0 {
  117. switch string((*raw)[0]) {
  118. case "\"":
  119. var s string
  120. if err := json.Unmarshal(*raw, &s); err != nil {
  121. return err
  122. }
  123. e.Message = &s
  124. case "{":
  125. var m map[string]interface{}
  126. if err := json.Unmarshal(*raw, &m); err != nil {
  127. return err
  128. }
  129. if v, ok := m["value"]; ok {
  130. if s, ok := v.(string); ok {
  131. e.Message = &s
  132. }
  133. }
  134. default:
  135. return fmt.Errorf("unrecognised error message: %#v", string(*raw))
  136. }
  137. }
  138. return nil
  139. }
  140. func (e Error) String() string {
  141. sl := make([]string, 0)
  142. if e.Code != nil {
  143. sl = append(sl, *e.Code)
  144. }
  145. if e.Message != nil {
  146. sl = append(sl, *e.Message)
  147. }
  148. if e.InnerError != nil {
  149. if is := e.InnerError.String(); is != "" {
  150. sl = append(sl, is)
  151. }
  152. }
  153. return strings.Join(sl, ": ")
  154. }
  155. func (e Error) Match(errorText string) bool {
  156. re := regexp.MustCompile(errorText)
  157. return re.MatchString(e.String())
  158. }